一、实现原理
通过shader,将物体的透明度由下往上,从1到0渐变即可(只需要两圈顶点,底下一圈alpha设为1,上面一圈alpha设为0)
二、实现步骤
1、创建geometry
这个几何体类似于圆柱,只是没有上下两个底面
/*let bottomPos = [];
let topPos = [];
let angleOffset = Math.PI*2/segment;
for(var i=0;i<segment;i++){
let x = Math.cos(angleOffset * i)*radius;
let z = Math.sin(angleOffset * i)*radius;
bottomPos.push(new Vector3(x,0,z));
topPos.push(new Vector3(x,height,z));
}
bottomPos = bottomPos.concat(topPos);
let face = []
for(var i=0;i<segment;i++){
if(i != segment - 1){
face.push(new Face3(i+segment+1,i,i+segment));
face.push(new Face3(i,i+segment+1,i+1));
}
else{
face.push(new Face3(segment,i,i+segment));
face.push(new Face3(i,segment,0));
}
}
let geo = new Geometry();
geo.vertices = bottomPos;
geo.faces = face;*/
//更新为BufferGeometry
let bottomPos = []
let topPos = []
let angleOffset = (Math.PI * 2) / segment
for (var i = 0; i < segment; i++) {
let x = Math.cos(angleOffset * i) * radius
let z = Math.sin(angleOffset * i) * radius
bottomPos.push(x, 0, z)
topPos.push(x, height, z)
}
bottomPos = bottomPos.concat(topPos)
let face = []
for (var i = 0; i < segment; i++) {
if (i != segment - 1) {
face.push(i + segment + 1, i, i + segment)
face.push(i, i + segment + 1, i + 1)
} else {
face.push(segment, i, i + segment)
face.push(i, segment, 0)
}
}
let geo = new BufferGeometry()
geo.setAttribute('position', new BufferAttribute(new Float32Array(bottomPos), 3))
geo.setIndex(new BufferAttribute(new Uint16Array(face), 1))
2、创建shader材质
let c = new Color(color);
let mat = new ShaderMaterial({
uniforms: {
targetColor:{value:new Vector3(c.r,c.g,c.b)},
height: { value: height},
},
side: DoubleSide,
transparent:true,
//depthTest:false,
depthWrite:false,
vertexShader: [
"varying vec3 modelPos;",
"void main() {",
" modelPos = position;",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
"uniform vec3 targetColor;",
"uniform float height;",
"varying vec3 modelPos;",
"void main() {",
" gl_FragColor = vec4(targetColor.xyz,(1.0 - modelPos.y/height)*(1.0 - modelPos.y/height));",
"}"
].join("\n")
});
需要注意的是使用双面渲染、打开透明、关闭深度写入以及alpha的获取(这里使用二次方程,比线性函数过度更自然)
三、完整代码
/**
* 生成并返回柱状渐变光环
* @param {*} radius 半径
* @param {*} height 高度
* @param {*} color 颜色
* @returns
*/
getAureole(radius,height,color){
let geo,mat;
let segment = 64;
//geo
{
/*let bottomPos = [];
let topPos = [];
let angleOffset = Math.PI*2/segment;
for(var i=0;i<segment;i++){
let x = Math.cos(angleOffset * i)*radius;
let z = Math.sin(angleOffset * i)*radius;
bottomPos.push(new Vector3(x,0,z));
topPos.push(new Vector3(x,height,z));
}
bottomPos = bottomPos.concat(topPos);
let face = []
for(var i=0;i<segment;i++){
if(i != segment - 1){
face.push(new Face3(i+segment+1,i,i+segment));
face.push(new Face3(i,i+segment+1,i+1));
}
else{
face.push(new Face3(segment,i,i+segment));
face.push(new Face3(i,segment,0));
}
}
geo = new Geometry();
geo.vertices = bottomPos;
geo.faces = face;*/
//更新为BufferGeometry
let bottomPos = []
let topPos = []
let angleOffset = (Math.PI * 2) / segment
for (var i = 0; i < segment; i++) {
let x = Math.cos(angleOffset * i) * radius
let z = Math.sin(angleOffset * i) * radius
bottomPos.push(x, 0, z)
topPos.push(x, height, z)
}
bottomPos = bottomPos.concat(topPos)
let face = []
for (var i = 0; i < segment; i++) {
if (i != segment - 1) {
face.push(i + segment + 1, i, i + segment)
face.push(i, i + segment + 1, i + 1)
} else {
face.push(segment, i, i + segment)
face.push(i, segment, 0)
}
}
geo = new BufferGeometry()
geo.setAttribute('position', new BufferAttribute(new Float32Array(bottomPos), 3))
geo.setIndex(new BufferAttribute(new Uint16Array(face), 1))
}
//mat
{
let c = new Color(color);
mat = new ShaderMaterial({
uniforms: {
targetColor:{value:new Vector3(c.r,c.g,c.b)},
height: { value: height},
},
side: DoubleSide,
transparent:true,
//depthTest:false,
depthWrite:false,
vertexShader: [
"varying vec3 modelPos;",
"void main() {",
" modelPos = position;",
" gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
"}"
].join("\n"),
fragmentShader: [
"uniform vec3 targetColor;",
"uniform float height;",
"varying vec3 modelPos;",
"void main() {",
" gl_FragColor = vec4(targetColor.xyz,(1.0 - modelPos.y/height)*(1.0 - modelPos.y/height));",
"}"
].join("\n")
});
}
let mesh = new Mesh(geo,mat);
mesh.renderOrder = 9999
return mesh;
}