前言

话说上一篇文章结尾讲到这一篇要做一个地球自转以及月球公转的三维动画,提笔,不对,是提键盘开始写的时候脑海中突然出现了几年前春晚风靡的那首歌:蒙古族小丫头唱的快乐的一家。闲言莫提,进入正题。

一、 原理分析

场景涉及两个对象,一个是地球、一个是月球,当然这基本是废话,不过还可以再添加一个对象,月球的公转轨迹。地球和月球都可以用一个球来模拟(Sphere),稍微困难的是公转轨迹,公转轨迹是一个圆,PhiloGL貌似没有直接提供圆的封装,但是有画线段的API,细细想来,什么是圆?祖冲之早就告诉我们了,所谓圆不过是多边形的无限逼近,那么我们就可以用多条细小的线段来逼近圆。地球自转很简单,而月球的公转就如同公转轨迹一样,只要将月球的位置设置到公转轨道上即可。

有了上述分析之后,我们就可以做出地球和月球的完美曲线来了。整体效果如下图所示:

二、 创建整体场景

创建整体场景即新建PhiloGL对象,配置GLSL、摄像头、灯光、贴图等。

PhiloGL('test1', {
program: [{
id: 'vertex',
from: 'defaults'
}, {
id: 'circle',
from: 'ids',
vs: 'shader-vs',
fs: 'shader-fs'
}],
camera: {
position: {
x: 0, y: 60, z: 60
}
},
textures: {
src: ['earth.jpg', 'moon.gif'],
},
onError: function (e) {
alert(e);
},
onLoad: function (app) {
var gl = app.gl,
canvas = app.canvas,
program = app.program,
camera = app.camera,
scene = app.scene,
view = new PhiloGL.Mat4; function clear() {
gl.viewport(0, 0, +canvas.width, +canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clearDepth(1);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
} clear(); function animate() {
......
} function drawScene() {            var lightConfig = scene.config.lights;
            lightConfig.enable = true;
            lightConfig.ambient = {
                r: +ambient.r,
                g: +ambient.g,
                b: +ambient.b
            };             // 线光源
            lightConfig.directional.direction = {
                x: +light.x,
                y: +light.y,
                z: +light.z
            };
            lightConfig.directional.color = {
                r: +light.r,
                g: +light.g,
                b: +light.b
            };             ......
        } function tick() {
animate();
drawScene();
scene.render();
PhiloGL.Fx.requestAnimationFrame(tick);
}
tick();
}
});

其中program下面包含两个glsl,第一个vertex是PhiloGL提供的默认GLSL,用于地球和月球。第二个circle用于月球公转轨道,定义如下:

<script id="shader-fs" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision highp float;
  #endif   varying vec4 vColor;   void main(void) {
    gl_FragColor = vColor;
  }
</script> <script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition;
  attribute vec4 aVertexColor;   uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;   varying vec4 vColor;   void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    vColor = aVertexColor;
  }
</script>

摄像头、灯光、贴图前面几篇文章已经介绍过,这里与之前基本相同,不再赘述。

三、 创建自转的地球

3.1 创建地球对象

创建Sphere对象并设置地球的贴图。代码如下:

var earth = new PhiloGL.O3D.Sphere({
    nlat: 30,
    nlong: 30,
    radius: 5,
    textures: ['earth.jpg'],
    colors: [1, 1, 1, 1],
    program: 'vertex'
});

3.2 自转

地球自转,位置无需变化,只需要随着时间让旋转角度递增即可。在外部设置旋转角度和旋转变量,此处旋转简单起见以Y轴为旋转轴,在animate方法中对旋转角度进行累加,在drawScene方法中设置earth对象进行旋转。

//外部设置旋转变量
var yRot = 1, ySpeed = 0.1;  // animate中旋转累加
yRot += ySpeed;  // drawScene中设置earth的旋转
earth.rotation.set(0, yRot, 0); 
earth.position.set(0, 0, 0);
earth.update();

这样就可实现地球的自转。

四、 创建公转的月球

4.1 创建月球

同样创建Sphere对象。

var moon = new PhiloGL.O3D.Sphere({
    nlat: 30,
    nlong: 30,
    radius: 1,
    textures: ['moon.gif'],
    colors: [1, 1, 1, 1],
    program: 'vertex'
});

4.2 公转

公转,只需要改变月球位置即可,让其位置处在公转圆上。

// 外部设置公转变量
var theta = 0, tSpeed = ySpeed / 30,
moon_x, moon_z, r = 30; // animate中设置公转位置
theta -= tSpeed;
moon_x = r * Math.cos(theta);
moon_z = r * Math.sin(theta); // drawScene中设置moon公转
moon.position.set(moon_x, 0, moon_z);
moon.update();

其中theta表示公转角度、tSpeed表示公转速度,其速度为地球自转速度的1/30,这里假设月球公转周期为30天,所以其公转速度为地球自转速度的1/30。地球以Y轴为旋转轴,假设月球的公转平面为XOZ平面,即Y值为0。根据三角函数可知,当旋转角度为θ时,X值为r * cos(θ),Z值为r * sin(θ),其中r为公转半径。

五、 创建公转轨道

有了上面几部分的分析之后,公转轨道也很容易了。首先,先来解释一下PhiloGL绘制线段的原理。

PhiloGL使用gl.drawArrays(gl.LINES, 0, count)来绘制多条线段,其中count表示线段的数量,当然这里需要使用之前的GLSL知识,我们要为aVertexPosition和aVertexColor这两个attribute变量赋值。如下:

program.circle.setBuffers({
    'aVertexPosition': {
        value: new Float32Array(points),
        size: 3
    },
    'aVertexColor': {
        value: new Float32Array(colors),
        size: 4
    }
});

其中points表示线段的点的集合,colors表示线段的端点的颜色,一条线段由两个端点组成,颜色也是由两个端点的颜色渐变而成。所以如果需要绘制连续的线段那么必须要将除首尾端点外全部重复,否则会造成线段断开。而此处绘制的是个封闭的圆,那么必须要在最后一条线段后再添加最后一个点到第一个点的线段,这样才能形成一个封闭的圆,颜色同样如此。代码如下:

function createRoute() {
    var points = [];
    var colors = [];
    for (var t = 0; t < Math.PI * 2; t += 0.01) {
        if (t == 0) {
            points.push(r * Math.cos(t), 0, r * Math.sin(t));
            colors.push(1,1,1,1);
        } else {
            points.push(r * Math.cos(t), 0, r * Math.sin(t));
            points.push(r * Math.cos(t), 0, r * Math.sin(t));
            colors.push(1,1,1,1);
            colors.push(1,1,1,1);
        }
    }
    points.push(r * Math.cos(0), 0, r * Math.sin(0));
    colors.push(1,1,1,1);
    return {"points": points, "colors": colors};
}

points存储所有点的集合,colors存储点的颜色的集合。第一个点仅存一次,其余点存两次,当循环结束后再将第一个点存入其中。将其结果赋给上面setBuffers中的两个变量。

设置好后,在drawScene中执行gl.drawArrays(gl.LINES, 0, count)即可。

六、 卫星的封装

写到这里,本来已经完事了,奈何程序员总是有强迫症的,你瞅瞅这月球、这轨道,这明显就是一个大对象嘛,那我必须要对其进行封装,变成卫星。将卫星半径、卫星轨道、公转速度、公转轨道夹角、轨道颜色等封装起来。整体代码如下:

function Satllite(radius, theta, speed, sigmaY, color, globeRadius) {
    this.sigmaY = sigmaY;
    this.color = color;
    this.radius = radius;
    this.speed = speed;
    this.theta = theta;
    this.globeRadius = globeRadius;
    this.circleLine = null;
    this.model = null;
    this.circleModel = null;     this.getModel = function () {
        if (this.model == null) {
            var mod = new PhiloGL.O3D.Sphere({
                nlat: 30,
                nlong: 30,
                radius: this.radius,
                textures: ['img/moon.gif'],
                colors: [1, 1, 1, 1],
                program: 'vertex'
            });
            this.model = mod;
        }
        return this.model;
    };     this.getCircleModel = function () {
        if (this.circleModel == null) {
            if (this.circleLine == null) {
                this.circleLine = this.getRoute();
            }
            var res = this.circleLine;
            var circle = new PhiloGL.O3D.Model({
                program: 'circle',
                render: function (gl, program, camera) {
                    program.setUniform('uMVMatrix', camera.view);
                    program.setUniform('uPMatrix', camera.projection);
                    gl.lineWidth(this.lineWidth || 1);
                    gl.drawArrays(gl.LINES, 0, res.points.length / 3);
                },
                attributes: {
                    aVertexPosition: {
                        size: 3,
                        value: new Float32Array(res.points)
                    },
                    aVertexColor: {
                        value: new Float32Array(res.colors),
                        size: 4
                    }
                }
            });
            this.circleModel = circle;
        }
        return this.circleModel;
    };     this.getRoute = function () {
        var points = [];
        var colors = [];
        for (var t = 0; t < Math.PI * 2; t += 0.05) {
            var pos = this.getPosition(t);
            if (t == 0) {
                points.push(pos.x, pos.y, pos.z);
                colors = colors.concat(this.color);
            } else {
                points.push(pos.x, pos.y, pos.z);
                points.push(pos.x, pos.y, pos.z);
                colors = colors.concat(this.color);
                colors = colors.concat(this.color);
            }
        }
        pos = this.getPosition(0);
        points.push(pos.x, pos.y, pos.z);
        colors = colors.concat(this.color);
        return {"points": points, "colors": colors};
    };     this.getPosition = function (thetaX) {
        x = this.globeRadius * Math.cos(thetaX) * Math.cos(this.sigmaY);
        y = this.globeRadius * Math.cos(thetaX) * Math.sin(this.sigmaY);
        z = this.globeRadius * Math.sin(thetaX);
        return {'x': x, 'y': y, 'z': z};
    };     this.getRealPosition = function () {
        return this.getPosition(this.theta);
    };     this.updateTheta = function () {
        this.theta -= this.speed;
    };
    
    this.updateModel = function () {
        if (this.model != null) {
            var pos = this.getRealPosition();
            this.model.position.set(pos.x, pos.y, pos.z);
            this.model.update();             this.circleModel.update();
        }
    };
};

radius表示卫星的半径, theta表示公转的角度, speed表示公转速度, sigmaY表示公转轨道与Y轴的夹角, color表示公转轨道颜色, globeRadius表示公转轨道半径。

getModel函数用于获取卫星实体对象;drawCircle函数用于绘制公转轨道;getRoute函数获取公转轨道信息,包括点位信息和颜色信息;getPosition函数用于计算当公转角度为theta时的位置坐标,其坐标值计算是在上文分析的基础上加入了Y轴旋转角度的影响;getRealPosition函数获取卫星公转实时位置信息;updateTheta函数用于更新卫星的旋转角度;updateModel直接更新卫星位置。

其调用方法如下:


// 创建
var sat = new Satllite(1, 0, 0.01, Math.PI, [1,1,1,1], 30);
sat.getModel();
scene.add(sat.model);// 加入场景 // animate中修改公转角度
sat.updateTheta(); // drawScene中绘制轨道以及更新位置
sat.updateModel();

多次按上述代码调用就能创建多个卫星对象,让地球的大家庭更加丰满,所以有了此类,只要知道了卫星轨道参数我们就能模拟出来地球外部的全部人造卫星,当然这还只是简单的情况,复杂的情况就要将轨道变成椭圆等等了。

之前做的时候轨道总是跟着地球一起旋转,不知什么原因,猜测是camera造成的,但是始终没有解决,后面我尝试将画圆对象封装成Model,结果完美解决了此问题。封装代码如下:

new PhiloGL.O3D.Model({
    program: 'circle',
    render: function (gl, program, camera) {
        program.setUniform('uMVMatrix', camera.view);
        program.setUniform('uPMatrix', camera.projection);
        gl.lineWidth(this.lineWidth || 1);
        gl.drawArrays(gl.LINES, 0, res.points.length / 3);
    },
    attributes: {
        aVertexPosition: {
            size: 3,
            value: new Float32Array(res.points)
        },
        aVertexColor: {
            value: new Float32Array(res.colors),
            size: 4
        }
    }
});

其中circle为上面定义的GLSL模块,attributes中是vs的两个attribute变量,这个与之前没有区别,一个控制点一个控制颜色。render函数是最终的渲染函数,两个setUniform设置camera,gl.drawArrays(gl.LINES, 0, res.points.length / 3)控制最终绘制,这与之前都没有区别,所以这里只是封装。有了此对象之后,其添加、更新等就与球等对象相同了。

七、 总结

本文简单介绍了绘制自转的地球以及公转的月球,看似很简单,其实中间有很多的坑,当然是因为自己确实水平有限,然而这正是我做此场景的本意,当然做完之后更加深刻的感受到了这一点。你看地球自转多么简单,月球公转多么简单,而人类才是地球上多么微不足道的一点点,你把自己搞那么复杂干什么,面对着永不停息转动的地球和月球你感受不到自己的渺小吗?再上升到整个宇宙不敢想象!所以,迈开自己的腿,多去看看更大的世界,不求能出得了宇宙,只求能够在有生之年走遍大部分地球,做一个见多识广的程序员,有一个快乐的一家!本系列文章写到这里,已经基本结束,后面如果有新的感悟也会继续写出。

PhiloGL学习(6)——深情奉献:快乐的一家的更多相关文章

  1. PhiloGL学习(2)——骚年,让我们荡起双桨

     前言 上一篇文章中简单介绍了PhiloGL框架如何上手.GLSL语言以及简单的绘制一个方块(见PhiloGL学习(1)--场景创建及二维方块加载).本文很简单,我们一起来让这个方块动起来.  一.  ...

  2. PhiloGL学习(1)——场景创建及方块欲露还羞出水面

    前言 上一篇文章中介绍了我认识PhiloGL框架的机缘以及初步的探讨(见JS前端三维地球渲染--中国各城市航空路线展示),在此文中仅仅对此框架进行了简单介绍并初步介绍了一些该框架的知识.首先三维这个东 ...

  3. PhiloGL学习(4)——三维对象、加载皮肤

    前言 上一篇文章中介绍了如何响应鼠标和键盘事件,本文介绍如何加载三维对象并实现给三维对象添加一个漂亮的皮肤. 一. 原理分析 我对三维的理解为:所谓三维对象无非是多个二维对象拼接到一起,贴图就更简单了 ...

  4. PhiloGL学习(5)——神说要有光,便有了光

    前言 上一篇文章中介绍了如何创建三维对象及加载皮肤,本文为大家介绍如何为场景添加光源. 一. 原理分析 光在任何地方都是非常重要的,无论在哪里都说是要发光发热,光和热也是分不开的.光线分为点光源和线光 ...

  5. PhiloGL学习(3)——程序员的法宝—键盘、鼠标

    前言 上一篇文章中介绍了如何让对象动起来,本文介绍如何让场景响应我们的鼠标和键盘以控制场景的缩放及对象的转动和移动等. 一. 原理分析 有了上一篇文章的基础,我们已经知道了如何让场景和对象动起来.本文 ...

  6. 学习.NET是因为热爱 or 兴趣 or 挣钱?

    看到最近园子里掀起了“.NET快不行了”.“.NET工资太低了”.“转行做XX”等一系列之风,不由得想说点什么,我只是基于自己的观点,你认同或者不认同,我就是这样认为,无所谓对与错,写文章就是为了交流 ...

  7. opengl入门学习

    OpenGL入门学习 说起编程作图,大概还有很多人想起TC的#include <graphics.h>吧? 但是各位是否想过,那些画面绚丽的PC游戏是如何编写出来的?就靠TC那可怜的640 ...

  8. 20145213《Java程序设计》第七周学习总结

    20145213<Java程序设计>第七周学习总结 教材学习内容总结 周末快乐的时间总是短暂的,还没好好感受就到了要写博客的周日.有人喟叹时间都去哪儿了,那本周我们就来认识一下Java里的 ...

  9. 雅思创始人Keith Taylor谈英语学习

    雅思创始人Keith Taylor谈英语学习 “要学的是信息,而不是语言” 我们要学习一个国家的语言就得知道这个国家的方方面面.要学习英语就得了解英美国家的社会.经济.人文.历史等各方面的信息. 大家 ...

随机推荐

  1. eastcom——eclipse中运行vtmserver项目

    1, vtmserver项目必须在tomcat7上运行. 2, 在Eclipse中vtmserver的截图 3, 在eclipse中配置一个tomcat7并将vtmserver加入其中 4, 在ecl ...

  2. Oracle_Sequence如何初始化开始值

    Sequence的start with 值如何确定才能保证生成的主键不会冲突??? 我的项目中最开始数据库表主键的生成策略是 increment,但由于后来采用了集群部署的方式,出现了主键冲突的问题. ...

  3. 深入理解计算机系统(2.7)------二进制小数和IEEE浮点标准

    整数的表示和运算我们已经讲完了,在实际应用中,整数能够解决我们大部分问题.但是某些需要精确表示的数,比如某件商品的价格,某两地之间的距离等等,我们如果用整数表示将会有很大的出入,这时候浮点数就产生了. ...

  4. angular之表单验证与ngMessages

    刚接触angular1.x很多经常用到的ngMessages的地方,这里顺便记一下,效果如下图: 如果引用了angular-messages.js报如下错误,说明你的angular.js和angula ...

  5. 西邮linux兴趣小组2014纳新免试题(三)

    [第三关] 题目 http://sortsth.sinaapp.com/ 分析 查看网页源码,得知题目让找出6种排序算法,每次刷新或提交序列都变化. 15种算法清单: CountingSort     ...

  6. Codevs1380没有上司的舞会_KEY

    没有上司的舞会 1380 没有上司的舞会 时间限制: 1 s 空间限制: 128000 KB 题目描述 Description Ural大学有N个职员,编号为1~N.他们有从属关系,也就是说他们的关系 ...

  7. 浅析Spring MVC工作机制

    1.如何使用Spring MVC? 在web.xml中配置一个DispatcherServlet DispatchServlet初始化的时候会去寻找一个在应用程序的WEB-INF目录下的配置文件,命名 ...

  8. VC++:创建,调用Win32动态链接库

    VC++:创建,调用Win32动态链接库 概述 DLL(Dynamic Linkable Library)动态链接库,Dll可以看作一种仓库,仓库中包含了可以直接使用的变量,函数或类.仓库的发展史经历 ...

  9. 通过SQL脚本导入数据到不同数据库避免重复导入三种方式

    前言 无论何种语言,一旦看见代码中有重复性的代码则想到封装来复用,在SQL同样如此,若我们没有界面来维护而且需要经常进行的操作,我们会写脚本避免下次又得重新写一遍,但是这其中就涉及到一个问题,这个问题 ...

  10. Operating system hdu 2835 OPT

    Operating system Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...