这一节,给导航盒添加一个点击后高亮的材质,我们计划它有以下几个功能:
1.点击A面的时候A逐渐高亮,点击B面B面逐渐高亮且A逐渐恢复
2.如果移动视角,被选中的面逐渐恢复对于第一个功能,首先遇到的问题是如何知道是哪个面高亮?我们在准备网格基础信息的时候准备了normal,我们可以用一个Vector3去储存点中面的方向信息,让它和normal做点乘,如果值大于0.999,就说明它是选中的面,需要渐变的话还需要一个float字段去控制强度,合并一起就是使用一个Vector4去控制一个面,点击并不是一个短时间频繁的过程,我们用一个Vector4 select 控制“渐亮”过程,一个Vector4 preSelect 控制“渐暗”过程就够用了。
varying vec3 vNormal;
varying vec2 vUV;
uniform sampler2D _MainTex;
uniform vec4 selectData;
uniform vec4 preSelectData;
uniform vec3 selectColor;
.......
vec3 col = texture2D(_MainTex,vUV).rgb;
float selectDotN = dot(selectData.rgb,vNormal);
vec3 selectCol;
if(selectDotN 0.999){
selectCol = lerp(vec(1.),selectColor,selectData.rgb);
}
else{
selectCol = vec3(1.)
}
//上面过程用lerp合并一下就是
vec3 selectCol1 = lerp(vec3(1.),lerp(vec3(1.),selectColor,step(0.99,dot(selectData.rgb,vNormal))),selectData.w);
vec3 selectCol2 = lerp(vec3(1.),lerp(vec3(1.),selectColor,step(0.99,dot(preSelectData.rgb,vNormal))),preSelectData.w);
//最后用得到的值乘以结果颜色
col*=selectCol1 *selectCol2;
Shader的要点解释完毕,我们给代码里面添加创建ShaderMaterial的函数:
interface IDirectBoxMaterial {
material: BABYLON.ShaderMaterial;
select: BABYLON.Vector4;
preSelect: BABYLON.Vector4;
selectColor: BABYLON.Color3;
}
class DirectBoxCreator {
......
private static createMaterial(scene: BABYLON.Scene): IDirectBoxMaterial {
BABYLON.Effect.ShadersStore['DirectBoxMatVertexShader'] = `
precision highp float;
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
uniform mat4 world;
uniform mat4 viewProjection;
varying vec3 vNormal;
varying vec2 vUV;
void main(void){
vUV = uv;
vNormal = normal;
gl_Position = viewProjection * world * vec4(position,1.);
}
`
BABYLON.Effect.ShadersStore['DirectBoxMatFragmentShader'] = `
precision highp float;
varying vec3 vNormal;
varying vec2 vUV;
uniform sampler2D _MainTex;
uniform vec4 selectData;
uniform vec4 preSelectData;
uniform vec3 selectColor;
vec3 lerp(vec3 v1,vec3 v2,float n){
return (1.-n)*v1+n*v2;
}
void main(void){
vec3 color = texture2D(_MainTex,vUV).rgb;
vec3 selectCol1 = lerp(vec3(1.),lerp(vec3(1.),selectColor,step(0.99,dot(selectData.rgb,vNormal))),selectData.w);
vec3 selectCol2 = lerp(vec3(1.),lerp(vec3(1.),selectColor,step(0.99,dot(preSelectData.rgb,vNormal))),preSelectData.w);
color*= selectCol1 * selectCol2;
gl_FragColor = vec4(color,1.);
}
`
const material = new BABYLON.ShaderMaterial("DirectBoxMat", scene, "DirectBoxMat", {
attributes: ['position', 'uv', 'normal'],
uniforms: ['selectData', 'preSelectData', 'world', 'viewProjection', 'selectColor'],
samplers: ['_MainTex']
})
const selectData = new B.Vector4(0, 0, 0, 0);
const preSelectData = new B.Vector4(0, 0, 0, 0);
const selectColor = new B.Color3(1, 0, 0);
material.setVector4("selectData", selectData);
material.setVector4("preSelectData", preSelectData);
material.setColor3("selectColor", selectColor);
return {
material,
select: selectData,
preSelect: preSelectData,
selectColor,
}
}
}
这段也可以看作一个简单的自定义生成ShaderMaterial的过程,如果有更复杂的ShaderMaterial的需求,在这个过程的关键点位增减自定义参数就可。之后使用这个函数创建一个Material代替之前创建的StandardMaterial,并将Material连同自定义uniform变量传到外面去,具体修改见(https://playground.babylonjs.com/?#ENABP9#19)
自定义材质准备好了之后,我们要开始研究怎么动。我们需要先将上一次选择的select丢到preSelect,根据获取的法线方向重新配置select, 然后在动画过程中计算二者的w让一个恢复,一个高亮:
const tmpV3 = B.TmpVectors.Vector3[0];
switch (direction) {
case CameraDirection.forward:
tmpV3.set(0, 0, -1);
break;
case CameraDirection.backward:
tmpV3.set(0, 0, 1);
break;
case CameraDirection.right:
tmpV3.set(1, 0, 0);
break;
case CameraDirection.left:
tmpV3.set(-1, 0, 0);
break;
case CameraDirection.up:
tmpV3.set(0, 1, 0);
break;
case CameraDirection.down:
tmpV3.set(0, -1, 0);
break;
}
const preSelect = this.materialData.preSelect;
const select = this.materialData.select;
preSelect.copyFrom(select);
select.set(tmpV3.x, tmpV3.y, tmpV3.z, 0);
const duration = 0.5;
this.process.play(duration, (process) => {
this.bindCamera.alpha = BABYLON.Scalar.Lerp(startAlpha, alpha, process);
this.bindCamera.beta = BABYLON.Scalar.Lerp(startBeta, beta, process);
preSelect.w = Math.max(0, preSelect.w - 1 / duration / 60);
select.w = Math.min(1, select.w + 1 / duration / 60);
})
对于第二个问题,解决方法很简单,当视角移动后,函数alignCameraToBindCamera会被调用,我们可以通过ProcessAnimation获取是否被动画控制的状态,通过记录是否高亮 alpha和beta判断现在是否是高亮对齐的状态,通过这两个状态来判断是否要把高亮恢复成默认状态:
//在移动结束后记录状态
moveCameraToDirection(direction: CameraDirection) {
......
this.process.play(duration, (process) => {
this.bindCamera.alpha = BABYLON.Scalar.Lerp(startAlpha, alpha, process);
this.bindCamera.beta = BABYLON.Scalar.Lerp(startBeta, beta, process);
preSelect.w = Math.max(0, preSelect.w - 1 / duration / 60);
select.w = Math.min(1, select.w + 1 / duration / 60);
}, () => {
//这里*****************
this._isLastAlign = true;
this._lastAlignAlpha = this.bindCamera.alpha;
this._lastAlignBeta = this.bindCamera.beta;
//这里*****************
})
......
}
//在摄像机矩阵变换后判断状态
alignCameraToBindCamera() {
this.selfCamera.alpha = this.bindCamera.alpha;
this.selfCamera.beta = this.bindCamera.beta;
if (this.bindCamera.radius * this.selfCamera.radius < 0) {
this.selfCamera.radius *= -1;
}
//这里*****************
if (this.process.isPlaying) return;
if (!this._isLastAlign) return;
if (Math.abs(this._lastAlignAlpha - this.bindCamera.alpha) > 0.1 || Math.abs(this._lastAlignBeta - this.bindCamera.beta) > 0.1) {
this._isLastAlign = false;
const select = this.materialData.select;
this.process.play(duration, (process) => {
select.w = Math.max(0, select.w - 1 / duration / 60);
})
//这里*****************
}
}
最后完成了第二个目标,此时的PG(https://playground.babylonjs.com/?#ENABP9#20)