Threejs围墙动画
threejs 围墙动画、围墙shader
·
Threejs围墙动画
难点1:
根据顶点构建出来的围墙,默认是z轴朝上。假如模型是y轴朝上,则不好控制围墙的边界顶点。
这里的思路是 把模型绕x轴翻转90度,然后根据rayser射线选取顶点。根据顶点构建完围墙后,再把围墙mesh翻转90度回来
代码:
function addShape() {
let c = [53.95640312236819, 193.4562016693937, 123.4327246849135, 187.81369736193992,
125.41600819650955, 218.75893553744257, 51.14432668036085, 223.0195030041947, 53.95640312236819, 193.4562016693937];
let posArr = [];
let uvrr = [];
let h = 10; //围墙拉伸高度
for (let i = 0; i < c.length - 2; i += 2) {
// 围墙多边形上两个点构成一个直线扫描出来一个高度为h的矩形
// 矩形的三角形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);
// 注意顺序问题,和顶点位置坐标对应
uvrr.push(0, 0, 1, 0, 1, 1);
uvrr.push(0, 0, 1, 1, 0, 1);
}
let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
// 设置几何体attributes属性的位置uv属性
geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvrr), 2);
geometry.computeVertexNormals()
let mesh = new THREE.Mesh(geometry, custMaterial); //网格模型对象Mesh
scene.add(mesh);
mesh.rotateX(-Math.PI / 2);
难点2:
围墙的shader编写,这个shader是拷贝的maptalks里面的围墙shader,查看源代码直接拿来用即可。
代码:
const vertexs = {
normal_vertex: "\n precision lowp float;\n precision lowp int;\n "
.concat(
THREE.ShaderChunk.fog_pars_vertex,
"\n varying vec2 vUv;\n void main() {\n vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n "
)
.concat(THREE.ShaderChunk.fog_vertex, "\n }\n"),
};
const fragments = {
rippleWall_fragment:
"\n precision lowp float;\n precision lowp int;\n uniform float time;\n uniform float opacity;\n uniform vec3 color;\n uniform float num;\n uniform float hiz;\n\n varying vec2 vUv;\n\n void main() {\n vec4 fragColor = vec4(0.);\n float sin = sin((vUv.y - time * hiz) * 10. * num);\n float high = 0.92;\n float medium = 0.4;\n if (sin > high) {\n fragColor = vec4(mix(vec3(.8, 1., 1.), color, (1. - sin) / (1. - high)), 1.);\n } else if(sin > medium) {\n fragColor = vec4(color, mix(1., 0., 1.-(sin - medium) / (high - medium)));\n } else {\n fragColor = vec4(color, 0.);\n }\n\n vec3 fade = mix(color, vec3(0., 0., 0.), vUv.y);\n fragColor = mix(fragColor, vec4(fade, 1.), 0.85);\n gl_FragColor = vec4(fragColor.rgb, fragColor.a * opacity * (1. - vUv.y));\n }\n",
};
const custMaterial = new THREE.ShaderMaterial({
uniforms: {
time: {
type: "pv2",
value: 0,
},
color: {
type: "uvs",
value: new THREE.Color("#FF4127"),
},
opacity: {
type: "pv2",
value: 1.0,
},
num: {
type: "pv2",
value: 10,
},
hiz: {
type: "pv2",
value: 0.15,
},
},
vertexShader: vertexs.normal_vertex,
fragmentShader: fragments.rippleWall_fragment,
blending: THREE.AdditiveBlending,
transparent: !0,
depthWrite: !1,
depthTest: !0,
side: THREE.DoubleSide,
});
然后循环函数里面控制时间参数。
function animal() {
renderer.render(scene, camera)
if (custMaterial) {
custMaterial.uniforms.time.value += 0.015;
}
requestAnimationFrame(animal)
}
demo代码
shader代码来源于maptalks
<template>
<div ref="test" class="test">
</div>
</template>
<script setup>
import * as THREE from "three";
import {onMounted, ref} from "vue";
import {MapControls} from 'three/examples/jsm/controls/OrbitControls'
const vertexs = {
normal_vertex: "\n precision lowp float;\n precision lowp int;\n "
.concat(
THREE.ShaderChunk.fog_pars_vertex,
"\n varying vec2 vUv;\n void main() {\n vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n "
)
.concat(THREE.ShaderChunk.fog_vertex, "\n }\n"),
};
const fragments = {
rippleWall_fragment:
"\n precision lowp float;\n precision lowp int;\n uniform float time;\n uniform float opacity;\n uniform vec3 color;\n uniform float num;\n uniform float hiz;\n\n varying vec2 vUv;\n\n void main() {\n vec4 fragColor = vec4(0.);\n float sin = sin((vUv.y - time * hiz) * 10. * num);\n float high = 0.92;\n float medium = 0.4;\n if (sin > high) {\n fragColor = vec4(mix(vec3(.8, 1., 1.), color, (1. - sin) / (1. - high)), 1.);\n } else if(sin > medium) {\n fragColor = vec4(color, mix(1., 0., 1.-(sin - medium) / (high - medium)));\n } else {\n fragColor = vec4(color, 0.);\n }\n\n vec3 fade = mix(color, vec3(0., 0., 0.), vUv.y);\n fragColor = mix(fragColor, vec4(fade, 1.), 0.85);\n gl_FragColor = vec4(fragColor.rgb, fragColor.a * opacity * (1. - vUv.y));\n }\n",
};
const custMaterial1 = new THREE.ShaderMaterial({
uniforms: {
time: {
type: "pv2",
value: 0,
},
color: {
type: "uvs",
value: new THREE.Color("#FF4127"),
},
opacity: {
type: "pv2",
value: 1.0,
},
num: {
type: "pv2",
value: 10,
},
hiz: {
type: "pv2",
value: 0.15,
},
},
vertexShader: vertexs.normal_vertex,
fragmentShader: fragments.rippleWall_fragment,
blending: THREE.AdditiveBlending,
transparent: !0,
depthWrite: !1,
depthTest: !0,
side: THREE.DoubleSide,
});
let test = ref(null), renderer, scene, camera, controls, pmremGenerator
onMounted(() => {
initModel();
test.value.appendChild(renderer.domElement)
animal();
})
function animal() {
renderer.render(scene, camera)
if (custMaterial1) {
custMaterial1.uniforms.time.value += 0.015;
}
requestAnimationFrame(animal)
}
function initModel() {
renderer = new THREE.WebGLRenderer({
antialias: true, //开启锯齿
})
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.85
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
let width = test.value.clientWidth
let height = test.value.clientHeight
renderer.setPixelRatio(window.devicePixelRatio) //设置设备像素比率,防止Canvas画布输出模糊。
renderer.setSize(width, height) //设置渲染区域尺寸
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera(30, width / height, 1, 40000)
camera.up.set(0, 0 ,1)
controls = new MapControls(camera, renderer.domElement)
camera.position.set(50, 50, 50)
addShape()
scene.add(new THREE.AxesHelper(50))
let ambient = new THREE.AmbientLight(0xffffff, 0.8)
scene.add(ambient)
const hemiLight = new THREE.HemisphereLight(0x00AAFF, 0xFFAA00, 0.8);
scene.add(hemiLight);
console.log(scene)
}
function addShape() {
let c = [0,0, 10, 0, 10, 10, 0, 10, 0, 0]
let posArr = [];
let uvrr = [];
let h = 10; //围墙拉伸高度
for (let i = 0; i < c.length - 2; i += 2) {
// 围墙多边形上两个点构成一个直线扫描出来一个高度为h的矩形
// 矩形的三角形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);
// 注意顺序问题,和顶点位置坐标对应
uvrr.push(0, 0, 1, 0, 1, 1);
uvrr.push(0, 0, 1, 1, 0, 1);
}
let geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(posArr), 3);
// 设置几何体attributes属性的位置uv属性
geometry.attributes.uv = new THREE.BufferAttribute(new Float32Array(uvrr), 2);
geometry.computeVertexNormals()
let custMaterial = new THREE.MeshLambertMaterial({
color: 0X049ef4,
side : THREE.DoubleSide
})
let mesh = new THREE.Mesh(geometry, custMaterial1); //网格模型对象Mesh
scene.add(mesh);
// mesh.rotateX(-Math.PI / 2);
}
</script>
<style scoped lang="less">
.test {
height: 100%;
}
</style>
更多推荐
已为社区贡献1条内容
所有评论(0)