本章节分为3点:

  • 理解three.js 的渲染结构
  • 对canvas 进行响应式布局
  • 让canvas 画布自适应设备分辨率
  • canvas 画布的css尺寸和像素尺寸

1.three.js 的渲染结构

接下来我们对这个渲染过程做一个详细解释。

Three.js 封装了场景、灯光、阴影、材质、纹理和三维算法,让你不必再直接用WebGL 开发项目。

当然,我刚才说的是“不必再直接用WebGL”,有的时候我们会间接用到WebGL,比如自定义着色器。

three.js 在渲染三维场景时,需要创建很多对象,并将它们关联在一起。

下图便是一个基本的three.js 渲染结构。

解释一下上面的示意图:

  • Render渲染器:

Render是threejs的主要对象。当你讲一个场景Scene和一个摄像机Camera传递到渲染器的渲染方法中,渲染器便会将摄像机视椎体中三维场景渲染成一个二维图像显示在canvas画布中。

  • Scene场景对象

场景对象是树状结构的,其中包含了三维对象Object3D和灯光对象Light。

Object3D可以看作是直接被渲染出来的,Object3D是网格对象Mesh和集合对象Group的基类。

场景对象可以定义场景的背景色和雾效。

在场景对象的树状结构中,每个对象的变换信息都是相对的。

比如汽车和汽车里的人,人的位置是相对于汽车而言的,当汽车移动了,人的本地坐标位坐标位虽然不变,但其世界坐标位已经变了。

  • Camera相机

按理说相机对象是咋在场景里面的,但是相机对象不在它所看的场景里面,这就像我们自己看不见自己的眼睛一样。

因此相机对象可以独立于场景之外。

相机对象可以看到其他三维对象的子对象,这样相机就会随其父对象同步变换。

  • Mesh网格对象

网格对象由几何体(Geometry)和材质(Material)2部分组成,Geometry负责塑形状,Material负责着色。

Geometry和Material可以被多个Mesh网格对象复用。

比如要绘制两个一模一样的立方体,那只需要实例化两个Mesh 即可,Geometry 和Materia可以使用一套。

  • Geometry几何体对象

几何体对象负责塑形状,存储了顶点相关的数据,比如:顶点点位,顶点索引,uv坐标等。

threejs中内置了许多基本几何体,我么也可以自定义几何体,或者从外部的模型文件里加载几何体。

  • Material材质对象

材质对象负责着色,绘制几何体的表面属性,比如:漫反射,镜面反射,光泽度,凹凸等。

  • Texture纹理对象

纹理对象就是一张图像。纹理图像的图像源可以是image图片,canvas画布,video视频等。

  • Light光源对象

Light对象不像Object3D那样依托于顶点,它更多的是像Object3D里面的材质Material,作用于物体的样式。

Light对象可以理解为:在几何体添加了材质之后,再利用光效配合材质对几何体的样式进行二次加工。

2-示例-绘制多个立方体

绘制一个立方体:
效果如下图:

渲染结构如下:

代码:
RenderStructure.tsx

import React, { useRef, useEffect } from "react";
import { BoxGeometry, DirectionalLight, Mesh, MeshNormalMaterial, MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";const { innerWidth, innerHeight } = window;const scene = new Scene();
const camera = new PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
camera.position.z = 5;const renderer = new WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);const geometry = new BoxGeometry();
const material = new MeshNormalMaterial();
const cube = new Mesh(geometry, material);
scene.add(cube);function animate() {requestAnimationFrame(animate);cube.rotation.x += 0.01;cube.rotation.y += 0.01;renderer.render(scene, camera);
}const RenderStructure: React.FC = (): JSX.Element => {const divRef = useRef<HTMLDivElement>(null);useEffect(() => {const { current } = divRef;if (current) {current.innerHTML = "";current.append(renderer.domElement);//  执行渲染动画animate();}}, []);return <div ref={divRef}></div>;
};export default RenderStructure;

在上面的代码中,我没有直接建立 ,而是在WebGLRenderer 对象的实例化方法里建立的,在其源码可以找到相关逻辑:

function WebGLRenderer( parameters = {} ) {const _canvas = parameters.canvas !== undefined ? parameters.canvas : createCanvasElement()……this.domElement = _canvas;……
}

通过WebGLRenderer 对象建立了canvas后,再在react的函数组件的useEffect hook 中,将canvas 添加到div 中。

const RenderStructure: React.FC = (): JSX.Element => {const divRef = useRef<HTMLDivElement>(null);useEffect(() => {const { current } = divRef;current && current.append(renderer.domElement);animate();}, []);return <div ref={divRef}></div>;
};

当前这个立方体的材质是MeshNormalMaterial,并不受光照影响。

2.如果想受光源的影响则给立方体换个MeshPhongMaterial 材质,再添加光源。

const geometry = new BoxGeometry();// 受光源的影响的材质
const material = new MeshPhongMaterial({ color: 0x44aa88 });const cube = new Mesh(geometry, material);
scene.add(cube);// 光的颜色
const color = 0xffffff;// 光照强度
const intensity = 1;// 实例化平行光
const light = new DirectionalLight(color, intensity);// 设置光源的位置(x,y,z)
light.position.set(-1, 2, 4);//光添加到场景中。
scene.add(light);

渲染效果如下:

  1. 在场景中添加2个一摸一样的立方体,可以发现:几何体和材质可被多个Mesh 对象共享。

共享的缺点:如果其中一个材质更改了,其他的也会跟着更改。

const geometry = new BoxGeometry();
const material = new MeshPhongMaterial({ color: 0x44aa88 });// 创建多个立方体。[-2, 0, 2]为立方体的位置
const cubes = [-2, 0, 2].map((num) => makeInstance(num));// 把立方体添加到场景中
scene.add(...cubes);function makeInstance(x: number) {const cube = new Mesh(geometry, material);cube.position.x = x;return cube;
}function animate() {requestAnimationFrame(animate);cubes.forEach((cube) => {cube.rotation.x += 0.01;cube.rotation.y += 0.01;});renderer.render(scene, camera);
}

当前的渲染结构如下所示:

渲染效果如下所示:

2.对canvas 进行响应式布局(当窗口宽度高度改变时,布局还是好的)

canvas画布有2种尺寸:

  1. 像素尺寸:即canvas画布在高度和宽度上有多少个像素,默认是300 * 150

  2. css尺寸:用css给canvas设置的尺寸。即css 里的width和height

canvas 的响应式布局需要考虑其像素尺寸。如果只考虑css尺寸则会对画布造成拉伸的效果。

案例:canvas 画布自适应浏览器的窗口的尺寸:

  1. 先取消renderer 的尺寸设置。
// 设置canvas的尺寸。
//renderer.setSize(innerWidth, innerHeight);

2.用css 设置canvas 画布及其父元素的尺寸,使其充满窗口。

  • src/view/ResponsiveDesign
const ResponsiveDesign: React.FC = (): JSX.Element => {……return <div ref={divRef} className="canvasWrapper"></div>;
};
  • src/view/fullScreen.css
html {height: 100%;
}
body {margin: 0;overflow: hidden;height: 100%;
}
#root,.canvasWrapper,canvas{width: 100%;height: 100%;
}

效果如下:我们可以看到立方体的边界出现了锯齿,这就是位图被css拉伸后失真导致的,默认canvas画布的尺寸只有300*150

因此,我们需要canvas的像素尺寸自适应窗口。

3.创建一个让canvas像素尺寸随css尺寸同步更新的方法:

// 让canvas 像素尺寸随css 尺寸同步更新的方法
resizeRendererToDisplaySize(renderer);// 将渲染尺寸设置为其显示的尺寸,返回画布像素尺寸是否等于其显示(css)尺寸的布尔值
function resizeRendererToDisplaySize(renderer) {// 像素尺寸:width, height// css尺寸: clientWidth, clientHeightconst { width, height, clientWidth, clientHeight } = renderer.domElement;const needResize = width !== clientWidth || height !== clientHeight;// 如果if (needResize) {// 设置canvas画布的尺寸基于像素尺寸设置。//false:不会基于clientWidth, clientHeight设置canvas的尺寸。会基于width, height设置canvas的尺寸。// renderer.setSize:设置canvas的尺寸renderer.setSize(clientWidth, clientHeight, false);}return needResize;
}
  • renderer.setSize(w,h,bool) 是重置渲染尺寸的方法,在此方法里会根据w,h参数重置canvas 画布的尺寸。
 this.setSize = function ( width, height, updateStyle ) {if ( xr.isPresenting ) {console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );return;}_width = width;_height = height;_canvas.width = Math.floor( width * _pixelRatio );_canvas.height = Math.floor( height * _pixelRatio );if ( updateStyle !== false ) {_canvas.style.width = width + 'px';_canvas.style.height = height + 'px';}this.setViewport( 0, 0, width, height );};

setSize() 方法中的bool 参数很重要,会用于判断是否设置canvas 画布的css 尺寸。

  1. 当canvas画布的尺寸变化了,相机适口的宽高比也需要同步调整。这样我们拖拽浏览器的边界,缩放浏览器的时候,就可以看到canvas 画布自适应浏览器的尺寸了。
function animate() {requestAnimationFrame(animate);if (resizeRendererToDisplaySize(renderer)) {const { clientWidth, clientHeight } = renderer.domElement;// camera.aspect:设置相机适口的宽高比例:camera.aspect = clientWidth / clientHeight;// 更新相机的投影矩阵camera.updateProjectionMatrix();}cubes.forEach((cube) => {cube.rotation.x += 0.01;cube.rotation.y += 0.01;});renderer.render(scene, camera);
}
  • camera.aspect 属性是相机视口的宽高比
  • 我们在WebGL 里说透视投影矩阵的时候说过,当相机视口的宽高比变了,相机的透视投影矩阵也会随之改变,因此我们需要使用camera.updateProjectionMatrix() 方法更新透视投影矩阵。
  • 至于我们为什么不把更新相机视口宽高比的方法一起放进resizeRendererToDisplaySize()里,这是为了降低resizeRendererToDisplaySize() 方法和相机的耦合度。具体要不要这么做视项目需求而定。

效果如下:

完整代码如下:

import React, { useRef, useEffect } from "react";
import { BoxGeometry, DirectionalLight, Mesh, MeshNormalMaterial, MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";
import "./fullScreen.css";const { innerWidth, innerHeight } = window;
const scene = new Scene();
const camera = new PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
camera.position.z = 5;const renderer = new WebGLRenderer();
// renderer.setSize(innerWidth, innerHeight);const geometry = new BoxGeometry();
const material = new MeshPhongMaterial({ color: 0x44aa88 });
// const cube = new Mesh(geometry, material);
// scene.add(cube);const cubes = [-2, 0, 2].map((num) => makeInstance(num));
scene.add(...cubes);
function makeInstance(x: number) {const cube = new Mesh(geometry, material);cube.position.x = x;return cube;
}const color = 0xffffff;
const intensity = 1;
const light = new DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);function animate() {requestAnimationFrame(animate);if (resizeRendererToDisplaySize(renderer)) {const { clientWidth, clientHeight } = renderer.domElement;camera.aspect = clientWidth / clientHeight;camera.updateProjectionMatrix();}cubes.forEach((cube) => {cube.rotation.x += 0.01;cube.rotation.y += 0.01;});renderer.render(scene, camera);
}// 将渲染尺寸设置为其显示的尺寸,返回画布像素尺寸是否等于其显示(css)尺寸的布尔值
function resizeRendererToDisplaySize(renderer: WebGLRenderer): boolean {const { width, height, clientWidth, clientHeight } = renderer.domElement;const needResize = width !== clientWidth || height !== clientHeight;if (needResize) {renderer.setSize(clientWidth, clientHeight, false);}return needResize;
}const ResponsiveDesign: React.FC = (): JSX.Element => {const divRef = useRef<HTMLDivElement>(null);useEffect(() => {const { current } = divRef;if (current) {current.innerHTML = "";current.append(renderer.domElement);animate();}}, []);return <div ref={divRef} className="canvasWrapper"></div>;
};
export default ResponsiveDesign;

制作如下页面:

Illustration.tsx

import React, { useRef, useEffect, useState } from "react";
import { BoxGeometry, DirectionalLight, Mesh, MeshNormalMaterial, MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";
import "./Illustration.css";const { innerWidth, innerHeight } = window;
const scene = new Scene();
const camera = new PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
camera.position.z = 5;const renderer = new WebGLRenderer();
// renderer.setSize(innIllustrationIllustrationrWidth, innerHeight);const geometry = new BoxGeometry();
const material = new MeshPhongMaterial({ color: 0x44aa88 });
// const cube = new Mesh(geometry, material);
// scene.add(cube);
const cubes = [-2, 0, 2].map((num) => makeInstance(num));
scene.add(...cubes);
function makeInstance(x: number) {const cube = new Mesh(geometry, material);cube.position.x = x;return cube;
}const color = 0xffffff;
const intensity = 1;
const light = new DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);function animate() {requestAnimationFrame(animate);if (resizeRendererToDisplaySize(renderer)) {const { clientWidth, clientHeight } = renderer.domElement;camera.aspect = clientWidth / clientHeight;camera.updateProjectionMatrix();}cubes.forEach((cube) => {cube.rotation.x += 0.01;cube.rotation.y += 0.01;});renderer.render(scene, camera);
}// 将渲染尺寸设置为其显示的尺寸,返回画布像素尺寸是否等于其显示(css)尺寸的布尔值
function resizeRendererToDisplaySize(renderer: WebGLRenderer): boolean {const { width, height, clientWidth, clientHeight } = renderer.domElement;const needResize = width !== clientWidth || height !== clientHeight;if (needResize) {renderer.setSize(clientWidth, clientHeight, false);}return needResize;
}const Illustration: React.FC = (): JSX.Element => {const divRef = useRef<HTMLDivElement>(null);let [btnState, setBtnState] = useState(["small", "放大"]);const toggle = () => {if (btnState[0] === "small") {setBtnState(["big", "缩小"]);} else {setBtnState(["small", "放大"]);}};useEffect(() => {const { current } = divRef;if (current) {current.innerHTML = "";current.append(renderer.domElement);animate();}}, []);return (<div className="cont"><p>立方体,也称正方体,是由6个正方形面组成的正多面体,故又称正六面体。它有12条边和8个顶点。其中正方体是特殊的长方体。立方体是一种特殊的正四棱柱、长方体、三角偏方面体、菱形多面体、平行六面体,就如同正方形是特殊的矩形、菱形、平行四边形一様。立方体具有正八面体对称性,即考克斯特BC3对称性,施莱夫利符号,考克斯特-迪肯符号,与正八面体对偶。</p><div className="inllustration"><div ref={divRef} className={`canvasWrapper ${btnState[0]}`}></div><button className="btn" onClick={toggle}>{btnState[1]}</button></div><p>立方体有11种不同的展开图,即是说,我们可以有11种不同的方法切开空心立方体的7条棱而将其展平为平面图形,见图1。 [2] 立方体的11种不同展开图。如果我们要将立方体涂色而使相邻的面不带有相同的颜色,则我们至少需要3种颜色(类似于四色问题)。立方体是唯一能够独立密铺三维欧几里得空间的柏拉图正多面体,因此立方体堆砌也是四维唯一的正堆砌(三维空间中的堆砌拓扑上等价于四维多胞体)。它又是柏拉图立体中唯一一个有偶数边面——正方形面的,因此,它是柏拉图立体中独一无二的环带多面体(它所有相对的面关于立方体中心中心对称)。将立方体沿对角线切开,能得到6个全等的正4棱柱(但它不是半正的,底面棱长与侧棱长之比为2:√3)将其正方形面贴到原来的立方体上,能得到菱形十二面体(RhombicDodecahedron)(两两共面三角形合成一个菱形)。</p><p>立方体的对偶多面体是正八面体。 当正八面体在立方体之内: 正八面体体积: 立方体体积=[(1/3)×高×底面积]×2: 边=(1/3)(n/2)[(n)/2]2: n=1: 6 星形八面体的对角线可组成一个立方体。截半立方体:从一条棱斩去另一条棱的中点得出 截角立方体超正方体:立方体在高维度的推广。更加一般的,立方体是一个大家族,即立方形家族(又称超方形、正测形)的3维成员,它们都具有相似的性质(如二面角都是90°、有类似的超体积公式,即Vn-cube=a等)。长方体、偏方面体的特例。</p><p>立方体是唯一能够独立密铺三维欧几里得空间的柏拉图正多面体,因此立方体堆砌也是四维唯一的正堆砌(三维空间中的堆砌拓扑上等价于四维多胞体)。它又是柏拉图立体中唯一一个有偶数边面——正方形面的,因此,它是柏拉图立体中独一无二的环带多面体(它所有相对的面关于立方体中心中心对称)。将立方体沿对角线切开,能得到6个全等的正4棱柱(但它不是半正的,底面棱长与侧棱长之比为2:√3)将其正方形面贴到原来的立方体上,能得到菱形十二面体(RhombicDodecahedron)(两两共面三角形合成一个菱形)。</p></div>);
};
export default Illustration;

Illustration.css

p {text-indent: 2em;line-height: 24px;font-size: 14px;
}.cont {width: 80%;max-width: 900px;margin: auto;
}
.inllustration{position: relative;float: left;
}
.canvasWrapper {margin-right: 15px;transition-property: width, height;transition-duration: 1s, 1s;
}.small {width: 150px;height: 150px;
}.big {width: 100%;height: 100%;
}.canvasWrapper canvas {width: 100%;height: 100%;
}.btn {position: absolute;top: 0;left: 0;cursor: pointer;
}

3.让canvas 画布自适应设备分辨率

不同设备的显示器的分辨率是不一样的。

以上图中的iPhone6/7/8 为例:

  • 375*667 代表的手机的屏幕的物理尺寸,如果我们在其中建立一个100% 充满屏幕的,那其尺寸就是375*667。
  • Dpr 代表像素密度,2 表示手机屏幕在宽度上有375*2 个像素,在高度上有667*2 个像素,因此iPhone6/7/8 的屏幕的像素尺寸就是750*1334。

当我们在这种像素尺寸大于物理尺寸的高分辨率显示器里绘图的时候,就需要考虑一个问题。

若我们直接在iPhone6/7/8 里建立一个充满屏幕的canvas,那其像素尺寸就是375*667。

这个尺寸并没发挥高分辨率显示器的优势,我们需要先将其像素尺寸设置为750*1334,然后再将其css 尺寸设置为375*667。

这样,就可以让canvas画布以高分辨率的姿态显示在显示器里。

代码示例:

function resizeRendererToDisplaySize(renderer: WebGLRenderer) {const { width, height, clientWidth, clientHeight } = renderer.domElement;// 上面的devicePixelRatio 就是设备像素密度,是window下的属性,即window.devicePixelRatio。const [w, h] = [clientWidth * window.devicePixelRatio, clientHeight * window.devicePixelRatio];const needResize = width !== w || height !== h;if (needResize) {renderer.setSize(w, h, false);}return needResize;
}

上面的devicePixelRatio 就是设备像素密度,是window下的属性,即window.devicePixelRatio。

其实,有的时候若不刻意观察,canvas 有没有自适应设备分辨率是很难看出的。

因此,若是对画面的渲染质量要求不高,可以什么都不做,这样也能避免canvas 画布像素尺寸变大后降低渲染效率的问题。

three - 3 - 基础知识(1. three渲染结构,2.对canvas 进行响应式布局,3.让canvas 画布自适应设备分辨率 )相关推荐

  1. linux文本运行层次,Linux基础知识之---文件系统层级结构

    #Linux基础知识之---文件系统层级结构 @(Linux系统)[基础知识,文件系统,层级结构,FHS] 一切皆文件: 在Linux中,无论是目录.配置.cpu.内存.键盘.鼠标.或者运行中的系统及 ...

  2. Android基础知识(二十):Notification、提醒式通知(横幅)踩坑与通知界面设置跳转

    Android基础知识(二十):Notification.提醒式通知(横幅)踩坑与通知界面设置跳转 一.Notification通知与基本用法 通知Notification是Android系统中比较有 ...

  3. c 结构体在声明时赋值_Java基础知识 初识Java 循环结构进阶 数组 数据类型 各种运算符...

    今天给大家带来的是初级Java基础部分的知识:包括初始Java.变量.常量.数据类型.运算符.各种选择结构.循环结构.数组等Java的基础语法部分!最后还有****循环结构的进阶****,步骤超详细, ...

  4. WPS-JS宏开发-基础知识-03-三大基本结构

    系统:Windows 11 软件:WPS表格11 本系列介绍一款类Excel的软件,WPS表格 当然也是介绍其宏开发,不同的是,使用的JS宏 会同样介绍多个系列,本系列介绍一些基础知识 Part 1: ...

  5. 【前端基础】12.CSS 基础知识学习——基本语法结构

    视频 目录 一,css基本规则规范 1.快捷方式 2.css注释 3.css命名规范 4.css书写规范 (1)空格规范 (2)选择器规范 (3)属性规范 5.css样式规则 二,字体样式 1.fon ...

  6. 计算机网络总线型结构优,计算机基础知识:计算机网络总线型拓扑结构的概述及优缺点...

    一.拓扑结构的概述 是一种研究与大小.形状无关的构成图形(线.面)的方法,即抛开网络中的具体设备,把工作站.服务器等网络单元抽象为"节点",把网络中的电缆等通信介质抽象为" ...

  7. 计算机基础知识的文献,四 计算机文献检索基础知识(原理、结构和功能)

    1.计算机检索原理 计算机一方面接受用户的检索提问,一方面从数据库中读取文献记录,然后把两者进行比较,即检索提问标识与文献记录标识进行匹配运算,如果比较的结果一致,那么这篇文献就会作为命中文献在检索结 ...

  8. C语言基础知识整理(选择结构)

    通过学习第三波知识你需要掌握: 关系运算符与关系表达式 逻辑运算符与逻辑表达式 if语句 switch语句 难点: if语句的嵌套 switch语句的使用 关系运算符: 其实关系运算符大家在实际的生活 ...

  9. web项目java程序的作用_基础知识(javaWeb工程目录结构)及各文件夹的作用

    在Eclipse中只要创建一个Dynamic Web Project,就可以根据创建向导创建出一个典型Java Web站点的目录结构.除非有特殊需要,在大多数情况下都没有必要修改这个目录结构,这也是W ...

最新文章

  1. 最近看Kafka源码,着实被它的客户端缓冲池技术优雅到了
  2. [Leetcode][LCP 19][JAVA][秋叶收藏集][动态规划]
  3. Linux常用命令 -- screen
  4. skywalking(4)
  5. Win7怎么设置文件共享 Win7共享设置方法
  6. 【跃迁之路】【671天】程序员高效学习方法论探索系列(实验阶段428-2018.12.15-16)...
  7. 关于linux内核中使用的时钟
  8. adt变频器故障代码ol2_误诊实例换来的变频器维修经验
  9. 传媒大学媒体中心资源批量获取工具的制作
  10. 指定的網域的名稱或安全性識別碼(用磁碟映像檔部署的電腦無法加入AD網域 )...
  11. 内置googletts并且内置中文语音包
  12. Pycharm进入debug模式后一直显示collecting data解决方法
  13. 《阳光下》 ——【传递正反馈】
  14. arduino入门教程!保姆级细致教学!
  15. fatal: detected dubious ownership in repository at ‘/home/
  16. Web应用中设置欢迎页面
  17. 苹果开机是白苹果黑屏_这是苹果应如何回应史诗般的1984年诱饵
  18. 一些操作系统安全设置
  19. 图片怎样修改分辨率?用什么修改图片分辨率?
  20. 【你知道么】90后们玩过体感游戏吗?

热门文章

  1. 有道云笔记不同步_有道云笔记无法同步如何处理?笔记无法同步解决方法介绍...
  2. office的加载项作用
  3. 支付宝集五福最全攻略,五分钟集齐五福!
  4. 基于规则的中文分词 - NLP中文篇
  5. 【Windows】realtek声卡升级驱动后没声音
  6. HTML5响应式手机模板:电商网站设计——仿淘宝手机app界面模板源码 HTML+CSS+JavaScript...
  7. #ArcGis中如何对属性表中的字段进行顺序赋值??
  8. 如何处理多重共线性问题
  9. 关于CC的完全非线性椭圆方程一书的一些小结
  10. Unity3D中如何调用序列帧图片为动画