首先我们创建一个mesh,不做任何旋转、平移和缩放:
let c = [
0, 0, //顶点1坐标
60, 0, //顶点2坐标
60, 80, //顶点3坐标
40, 120, //顶点4坐标
-20, 80, //顶点5坐标
0, 0, //顶点6坐标 和顶点1重合
]
let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
let posArr = [];
let h = 20; //围墙拉伸高度
for (let i = 0; i < c.length - 2; i += 2) {
// 三角形1 三个顶点坐标
posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], 0, c[i + 2], c[i + 3], h);
// 三角形2 三个顶点坐标
posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], h, c[i], c[i + 1], h);
}
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
geometry.computeVertexNormals();
let material = new THREE.MeshLambertMaterial({
color: 0xff0000, //三角面颜色
side: THREE.DoubleSide, //两面可见
transparent:true,
opacity:0.8
});
let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
model.add(mesh);
如下图:
现在使用着色器来给这个模型围一圈光带,注意,是要在z轴上来处理。
const vertexShader = `
varying vec3 vPosition;//表示顶点插值后位置数据,与片元数量相同,一一对应
void main(){
vPosition = position;// 顶点位置坐标插值计算
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
}
`;
const fragmentShader = `
varying vec3 vPosition;
void main() {
// 整体设置半透明色,记得Materia要开启透明
gl_FragColor = vec4(1.0,0.0,0.0,0.5);
if(vPosition.z > 10.0 && vPosition.z < 11.0){
gl_FragColor = vec4(1.0,1.0,0.0, 1.0);
}
}
`;
let material = new THREE.ShaderMaterial({
//顶点着色器对象vertexShader
vertexShader: vertexShader,
// 片元着色器对象fragmentShader
fragmentShader: fragmentShader,
transparent:true,
side:THREE.DoubleSide
});
效果如下:
此时,我们在顶点着色器里使用的是vPosition = position;来计算顶点位置插值。
if(vPosition.z > 10.0 && vPosition.z < 11.0){
gl_FragColor = vec4(1.0,1.0,0.0, 1.0);
}
我们在z轴上做了条件判断设置光带。
现在来对模型进行旋转缩放平移等操作。
mesh.rotateX(-Math.PI/4);
mesh.translateY(20);
mesh.scale.set(0.5,0.5,0.5);
光带依旧稳稳的围在模型上,相对位置没有变化。
现在让我们回到模型刚建立的时候,如图1,是一个立起来的围墙,但我们平时的围墙肯定是平下去的,所以我们需要将模型旋转一下,第一段代码稍作修改,最后加一个旋转操作。
let c = [
0, 0, //顶点1坐标
60, 0, //顶点2坐标
60, 80, //顶点3坐标
40, 120, //顶点4坐标
-20, 80, //顶点5坐标
0, 0, //顶点6坐标 和顶点1重合
]
let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
let posArr = [];
let h = 20; //围墙拉伸高度
for (let i = 0; i < c.length - 2; i += 2) {
// 三角形1 三个顶点坐标
posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], 0, c[i + 2], c[i + 3], h);
// 三角形2 三个顶点坐标
posArr.push(c[i], c[i + 1], 0, c[i + 2], c[i + 3], h, c[i], c[i + 1], h);
}
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
geometry.computeVertexNormals();
let material = new THREE.MeshLambertMaterial({
color: 0xff0000, //三角面颜色
side: THREE.DoubleSide, //两面可见
transparent:true,
opacity:0.8
});
// 为了让围墙平放,需要旋转模型
mesh.rotateX(-Math.PI/2);
现在围墙放平了。
这个时候再来给围墙加光带,这时候我们脑子里想到的肯定是在着色器里操作y轴,这样才能将模型围起来,这样写
if(vPosition.y > 10.0 && vPosition.y < 11.0){
gl_FragColor = vec4(1.0,1.0,0.0, 1.0);
}
const vertexShader = `
varying vec3 vPosition;//表示顶点插值后位置数据,与片元数量相同,一一对应
void main(){
vPosition = position;// 顶点位置坐标插值计算
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
}
`;
const fragmentShader = `
varying vec3 vPosition;
void main() {
// 整体设置半透明色
gl_FragColor = vec4(1.0,0.0,0.0,0.8);
if(vPosition.y > 10.0 && vPosition.y < 11.0){
gl_FragColor = vec4(1.0,1.0,0.0, 1.0);
}
}
结果光带效果如下,并没有如我们期望的围城一圈,秘密就在于vPosition的取值:
vPosition=postion时,获取的是顶点相对模型的坐标,在模型变化前,基于此时看到模型姿态,编写着色器代码,那么之后模型再发生变化,光带也会随之发生相对变化。
那如果已经改变了模型的姿态,比如上个例子里把模型旋转了90度,基于此时看到的模型姿态,则需要使用模型矩阵变换后插值。
试试一下代码:
const vertexShader = `
varying vec3 vPosition;//表示顶点插值后位置数据,与片元数量相同,一一对应
void main(){
// vPosition = position;// 顶点位置坐标插值计算
vPosition = vec3(modelMatrix * vec4( position, 1.0 ));
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 模型顶点坐标
gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4( position, 1.0 );
}
`;
const fragmentShader = `
varying vec3 vPosition;
void main() {
// 整体设置半透明色
gl_FragColor = vec4(1.0,0.0,0.0,0.8);
if(vPosition.y > 10.0 && vPosition.y < 11.0){
gl_FragColor = vec4(1.0,1.0,0.0, 1.0);
}
}
`;
光带又回到预期位置了。
此时执行mesh.translateZ(10);会发现模型发生了平移,但是光带的位置没有变化,依旧在y轴10的位置。
因为vPosition = vec3(modelMatrix * vec4( position, 1.0 ));获取的是顶点的相对坐标轴的位置,可以理解为绝对坐标,世界坐标,模型移动后,光带的位置仍然不变。
最后的总结:
想要着色器效果随着模型旋转平移缩放能跟随变化,需要在模型发生变化前,根据此时的模型姿态来编写着色器代码,使用vPosition = position;来获取顶点位置坐标插值。
vPosition = vec3(modelMatrix * vec4( position, 1.0 ));慎用。