赞
踩
很多同学看到许多智慧城市的效果,却始终苦苦无法学会,而且网上大多数特效都没有给到完整的开源代码。大家的学习之路,困难重重。
本人在学习智慧城市效果查阅资料的时候,也是花了大量的时间,网上太多的错误阉割代码。
所以在此把自己实现的效果和代码列出来,方便大家参考。
整体代码放在github上面,因为时间原因没整理了,代码比较乱… github
这里主要实现的功能有:飞线,粒子星空,防护墙,扩散圈,辉光图层特效,obj模型加载。
建筑物采用纹理贴图的方式,通过json数据来渲染。 看到网上很多都是用模型加载的方式,但是这种方式,在真实的场景中,不存在。真实的场景应该是可以变动的,所以采用json加载的方式。
找一张建筑物图片 (可以看到这张图,有两种横向方向的贴图,和32种竖向的贴图)
// 纹理贴图是把立方体拆成三角形去贴。6面体就需要12个三角形面
// position是该建筑在三维坐标系的坐标,x、y、z是长宽高
function createCube(position, x, y, z) {
var geometry = new THREE.CubeGeometry(x, y, z);
var material = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture("image/ny1.jpg"),
});
var temp1, temp2;
// 从图片中切出两个面。
var _bg1 = [
new THREE.Vector2(0, 0.5),
new THREE.Vector2(0.5, 0.5),
new THREE.Vector2(0.5, 1),
new THREE.Vector2(0, 1),
];
var _bg2 = [
new THREE.Vector2(0.875, 0.875),
new THREE.Vector2(1, 0.875),
new THREE.Vector2(1, 1),
new THREE.Vector2(0.875, 1),
];
geometry.faceVertexUvs[0] = []; // 然后把这两个面拆分,贴到12个三角形上面。形成我们要的六面体建筑
geometry.faceVertexUvs[0][0] = [temp1[3], temp1[0], temp1[2]];
geometry.faceVertexUvs[0][1] = [temp1[0], temp1[1], temp1[2]];
geometry.faceVertexUvs[0][2] = [temp1[3], temp1[0], temp1[2]];
geometry.faceVertexUvs[0][3] = [temp1[0], temp1[1], temp1[2]];
geometry.faceVertexUvs[0][4] = [temp2[3], temp2[0], temp2[2]];
geometry.faceVertexUvs[0][5] = [temp2[0], temp2[1], temp2[2]];
// geometry.faceVertexUvs[0][6] = [temp1[3], temp1[0], temp1[2]];
// geometry.faceVertexUvs[0][7] = [temp1[0], temp1[1], temp1[2]];
geometry.faceVertexUvs[0][8] = [temp1[3], temp1[0], temp1[2]];
geometry.faceVertexUvs[0][9] = [temp1[0], temp1[1], temp1[2]];
geometry.faceVertexUvs[0][10] = [temp1[3], temp1[0], temp1[2]];
geometry.faceVertexUvs[0][11] = [temp1[0], temp1[1], temp1[2]];
mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.position.z = position.z;
mesh.position.y = position.y;
mesh.position.x = position.x;
scene.add(mesh);
}
这里一个实现简单建筑的函数就完成了,真实的场景中,我们肯定会有不同类型的建筑存在,所以我们可以实现很多个不同的fn,来通过json调用渲染成我们要的建筑群。
飞线我们采用顶点着色器和片元着色器来做。 原理是画一个完整的线,然后通过改变空白长度的透明度,来实现。
先声明两个着色器:
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
attribute float percent;
uniform float u_time;
uniform float number;
uniform float speed;
uniform float length;
varying float opacity;
uniform float size;
void main()
{
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
float l = clamp(1.0-length,0.0,1.0);//空白部分长度
gl_PointSize = clamp(fract(percent*number + l - u_time*number*speed)-l ,0.0,1.) * size * (1./length);
opacity = gl_PointSize/size;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
varying float opacity;
uniform vec3 color;
void main(){
if(opacity <=0.2){
discard;
}
gl_FragColor = vec4(color,1.0);
}
</script>
然后绘制飞线:
let curve = new THREE.EllipseCurve(
0,
0, // ax, aY
10,
10, // xRadius, yRadius
0,
2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
);
initFlyLine(
curve,
{
speed: 1,
color: new THREE.Vector3(1.0, 0.0, 1.0),
number: 1.0,
length: 0.7,
size: 2.0,
},
500,
0,
{x:0,y:0,z:0}
);
// 根据curve和颜色 生成线条
/**
* @param curve {THREE.Curve} 路径,
* @param matSetting {Object} 材质配置项
* @param pointsNumber {Number} 点的个数 越多越细致
* @param position {Object} 位置
* */
function initFlyLine(curve, matSetting, pointsNumber,position) {
var points = curve.getPoints(pointsNumber);
var geometry = new THREE.BufferGeometry().setFromPoints(points);
let length = points.length;
var percents = new Float32Array(length);
for (let i = 0; i < points.length; i += 1) {
percents[i] = i / length;
}
geometry.attributes.percent = new THREE.BufferAttribute(percents, 1);
let lineMaterial = initLineMaterial(matSetting);
var flyLine = new THREE.Points(geometry, lineMaterial);
let euler = new THREE.Euler( // 有弧度的曲线
Math.random() * Math.PI,
Math.random() * Math.PI,
0
);
flyLine.position.x = position.x
flyLine.position.y = position.y
flyLine.position.z = position.z
flyLine.layers.enable(1)
scene.add(flyLine);
}
防护墙实现起来很简单,就实现一个圆柱体,里面是空心的(通过设置openEnded为true,或者设置材质的时候用数组,只贴图外围那个面)就好了,然后纹理贴图,贴一个渐变色的图。
/**
* @param r {Number} 半径,
* @param src {String} 纹理贴图的图片
* @param pointsNumber {Number} 点的个数 越多越细致
* @param position {Object} 位置
* */
function scatter3DCylinder(r, src) {
const geometry = new THREE.CylinderGeometry(r, r, 6, 20);
const circle = new THREE.Mesh(geometry, [
new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
transparent: true,
map: THREE.ImageUtils.loadTexture(src),
}),
]);
}
扩散圈和防护墙的实现差不多,也是先实现一个半球,然后通过动画使这个圆的半径和高度扩大,同时改变opcity的值。
// 圆扩散
function scatter3DCircle(r) {
const geometry = new THREE.SphereGeometry(
r,
120,
120,
0,
Math.PI * 2,
0,
Math.PI / 2
);
const circle = new THREE.Mesh(geometry, [
new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
transparent: true,
map: THREE.ImageUtils.loadTexture("image/gradual_red_01.png"),
}),
]);
// circle.rotation.x = -Math.PI / 2.0;
let s = 0,
p = 0;
function render() {
// animation
if (s > 160) {
(s = 0), (p = 160);
}
circle.scale.set(1 + s / 60, 1 + s / 80, 1 + s / 60);
circle.material[0].opacity = p / 160;
s++;
p--;
requestAnimationFrame(render);
}
render();
return circle;
}
辉光我觉得是一个比较重要的环姐,上面看的特效,基本被我加了辉光特效。所以看起来有光晕的效果。
首先我们得了解一个概念,图层:
threejs提供了层次的支持。和相机处于同一层次的对象可见,否则不可见。在threejs中,最多可以设置32层,默认的层次是1。层次在有些系统中很有用,可以将不同的模式的对象设成不同的层次,这样,切换模式就只需切换一下相机的层次就可以了。
辉光是直接作用于图层的,所以我们不能直接放在我们的渲染图层,我们可以给辉光放在单独的一个图层,然后把我们需要给辉光的物体模型,一个layers.enable,同时适用辉光图层就好了。
function addBloomPass() {
// 辉光 后期处理 // 后期处理可以用图层来实现单独图层渲染(把某个物体放到辉光渲染的图层) 其中 OutlinePass外轮廓的后期处理 可以单独作用于物体,不需要分图层
// layers.set() 设置某个对象为某个图层,会删除过去图层
// layers.enable() 为某个对象增加某个图层
// 注意物体的材质,有些材质不能用特效 最好用MeshBasicMaterial
renderScene = new THREE.RenderPass(scene, camera);
var bloomPass = new THREE.UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5,
0,
0
);
// bloomPass.renderToScreen = true;
const effectCopy = new THREE.ShaderPass(THREE.CopyShader); //传入了CopyShader着色器,用于拷贝渲染结果
effectCopy.renderToScreen = true;
const FXAAShader = new THREE.ShaderPass(THREE.FXAAShader);
bloomPass.threshold = 0;
bloomPass.strength = 2.5;
bloomPass.radius = 0;
composer = new THREE.EffectComposer(renderer);
composer.addPass(renderScene);
composer.addPass(bloomPass);
composer.addPass(effectCopy);
composer.render();
}
然后再animate方法里面添加:
function animate() {
requestAnimationFrame(animate);
// 渲染器清除颜色、深度或模板缓存. 此方法将颜色缓存初始化为当前颜色
renderer.clear();
controls.update();
camera.layers.set(1);
if (composer) { // 把辉光设置在图层1.
composer.render();
}
// 清除深度缓存
renderer.clearDepth();
camera.layers.set(0); // 然后把正常部分渲染在图层0
renderer.render(scene, camera);
}
之后我们想把某个物体添加到辉光可以直接:
Mesh.layers.enable(1);
粒子星空,是由无数个粒子渲染出来的。
在3D建模过程中,当我们需要创建很多细小的物体时,并不会一个个地创建这些物体,而是通过创建粒子,粒子可以模拟很多效果,例如烟花、火焰、雨滴、雪花、云朵等等。Three.js提供了各种的粒子系统创建方式。从官网例子的demo来看,可以总结分为两类,分别是Points和Sprite
这里我们使用points来实现
现在我们来实现粒子星空:
// 创建粒子星空
function createAi() {
var vertexHeight = 15000;
var planeDefinition = 100;
var planeSize = 1445000;
var totalObjects = 50000;
var geometry = new THREE.Geometry();
for (i = 0; i < totalObjects; i++) {
var vertex = new THREE.Vector3();
vertex.x = Math.random() * planeSize - planeSize * 0.5;
vertex.y = Math.random() * 100000 + 10000;
vertex.z = Math.random() * planeSize - planeSize * 0.5;
geometry.vertices.push(vertex);
geometry.colors.push(new THREE.Color(Math.random() * 0x00ffff));
}
var geometry2 = geometry.clone();
var material = new THREE.PointCloudMaterial({
size: 200,
vertexColors: true,
});
var particles = new THREE.PointCloud(geometry, material);
var material2 = new THREE.PointCloudMaterial({
size: 200,
vertexColors: true,
});
var particles2 = new THREE.PointCloud(geometry2, material2);
particles2.rotation.z = -Math.PI;
scene.add(particles);
scene.add(particles2);
}
模型加载比较简单,又尤为重要。
因为在我们做项目的过程中,很多复杂的模型,是不用自己去实现的,直接通过3d建模来实现模型,我们直接导入就可以了。同时我们还能对导入的模型进行材质或者点的修改。
使用三维软件导出.obj模型文件的时候,会同时导出一个材质文件.mtl, .obj和.stl文件包含的数据一样都是几何体对象的顶点位置、顶点法向量等顶点相关数据, 材质文件.mtl包含的是RGB颜色值等材质信息。
加载.obj三维模型的时候,可以只加载.obj文件,然后借助three.js引擎自定义材质对象Material,也可以同时加载obj和mtl文件
function loadAFEObj(){
var OBJLoader = new THREE.OBJLoader();//obj加载器
OBJLoader.load('./image/aifeier.obj', function(obj) {
obj.children.forEach((item,index)=>{ // 这里修改下材质,因为我们用的免费的模型,他没有带上mtl文件,所以我们就自己改一下材质,给点颜色变得好看些。
item.material = new THREE.MeshBasicMaterial({
color: 0x050918,
depthWrite: false,
side: THREE.DoubleSide,
transparent: true
})
})
obj.scale.set(0.2, 0.25, 0.2); //放大obj组对象
obj.position.x= 0
obj.position.y= 0
obj.position.z= 0
scene.add(obj);//返回的组对象插入场景中
})
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。