专栏首页点滴积累PhiloGL学习(6)——深情奉献:快乐的一家

PhiloGL学习(6)——深情奉献:快乐的一家

 前言

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

 一、 原理分析

场景涉及两个对象,一个是地球、一个是月球,当然这基本是废话,不过还可以再添加一个对象,月球的公转轨迹。地球和月球都可以用一个球来模拟(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)控制最终绘制,这与之前都没有区别,所以这里只是封装。有了此对象之后,其添加、更新等就与球等对象相同了。

 七、 总结

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Docker网络——单host网络

    前言 前面总结了Docker基础以及Docker存储相关知识,今天来总结一下Docker单主机网络的相关知识。毋庸置疑,网络绝对是任何系统的核心,他在Docke...

    魏守峰
  • 使用bokeh-scala进行数据可视化

    目录 前言 bokeh简介及胡扯 bokeh-scala基本代码 我的封装 总结 一、前言        最近在使用spark集群以及geotrellis框架(...

    魏守峰
  • Cesium几个案例介绍

    前言 本文为大家介绍几个Cesium的Demo,通过这几个Demo能够对如何使用Cesium有进一步的了解,并能充分理解Cesium的强大之处和新功能。其他的无...

    魏守峰
  • CMDB开发

    TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部...

    菲宇
  • React Native之React速学教程(中)

    React Native之React速学教程(中) 本文出自《React Native学习笔记》系列文章。 React Native是基于React的,在开发R...

    CrazyCodeBoy
  • 直播平台开发前要做的准备及注意事项

    1、产品定位:无论是开发什么样的直播APP,前期的市场分析是必不可少的。市场调研点就是要发现直播要给到企业的主要作用是什么,然后对功能的细化演绎。其次,就是发现...

    布谷安妮
  • 混合云计算部署的三个要求

    如今,许多IT专业人员倾向于采用混合云方法,让企业的不同工作负载在内部部署数据中心或在公共云中这样彼此独立的情况下运行。然而,对于大多数企业来说,采用更多的是混...

    静一
  • 这家公司融资30万英镑瞄准残疾人和老年人的度假需求

    ? Accomable公司帮助残疾人和老年人找到经过改装适应其需求的旅店,并为他们租用度假所需的特殊设备,完成了种子轮300000万英镑融资。 该笔资金由来自...

    点滴科技资讯
  • 【开源公告】H5、小程序自动化测试框架FAutoTest正式开源

    H5以及小程序越来越多,你的自动化测试跟上了吗? ? 随着业务的发展,许多项目中H5以及小程序占比逐渐增多,因为快速建设相关的自动化来提高项目的效率和质量成为...

    腾讯开源
  • 谷歌AI医疗新成果:将转移性乳腺癌检测准确率推向了几乎完美的99%

    在国家癌症中心发布的《2017年中国肿瘤的现状和趋势》报告中显示,乳腺癌的发病率已经位列女性恶性肿瘤之首。

    镁客网

扫码关注云+社区

领取腾讯云代金券