Three.js中實(shí)現(xiàn)流動(dòng)的管線效果的全過程
最近在跟第三方對(duì)接工業(yè)流程的模型,發(fā)現(xiàn)建模的管線還挺好看的,給材質(zhì)加上一個(gè)偏移,就會(huì)出現(xiàn)一直流動(dòng)的效果,于是在想可不可以使用Three.js畫出同等效果的管線。
Three.js文檔:three.js docs
整體的實(shí)現(xiàn)思路是先繪制管線模型,然后在模型上添加一個(gè)動(dòng)態(tài)的著色器材質(zhì),實(shí)現(xiàn)管線流動(dòng)效果。
首先我想通過使用CatmullRomCurve3和TubeGeometry這倆個(gè)類API去創(chuàng)建平滑的 3D 樣條曲線。


代碼如下
/**
* 繪制管線
* @param THREE
* @param scene
* @param points
*/
function createPipeLine(THREE, scene ,points) {
const cps = points.map(item=>{
return new THREE.Vector3(...item)
})
console.log(cps,'cps')
const curve = new THREE.CatmullRomCurve3(cps)
let geometry = new THREE.TubeGeometry(curve, 100, 10, 32)
const material = new THREE.MeshLambertMaterial({
color: '#19bbd5',
side: THREE.DoubleSide,
})
let length = curve.getLength()
let uniforms = {
totalLength: { value: length },
stripeOffset: { value: 0 }, // 條紋偏移量
stripeWidth: { value: 100 }, // 條紋寬度 (歸一化值)
stripeSpacing: { value: 100 }, // 條紋間距 (歸一化值)
stripeColor: { value: new THREE.Color('#096be3') }, // 條紋顏色
speedFactor: { value: 50 },
};
material.onBeforeCompile = shader => {
shader.uniforms.totalLength = uniforms.totalLength;
shader.uniforms.stripeOffset = uniforms.stripeOffset;
shader.uniforms.stripeWidth = uniforms.stripeWidth;
shader.uniforms.stripeSpacing = uniforms.stripeSpacing;
shader.uniforms.stripeColor = uniforms.stripeColor;
shader.fragmentShader = `
uniform float totalLength;
uniform float stripeOffset;
uniform float stripeWidth;
uniform float stripeSpacing;
uniform vec3 stripeColor;
${shader.fragmentShader}
`.replace(
`#include <color_fragment>`,
`#include <color_fragment>
// 計(jì)算條紋模式
float pattern = mod((vUv.x - stripeOffset) * totalLength / (stripeWidth + stripeSpacing), 1.0);
float isStripe = step(pattern, stripeWidth / (stripeWidth + stripeSpacing));
// 平滑邊緣
float edge = fwidth(vUv.x) * 2.0;
float smoothFactor = smoothstep(0.0, edge, abs(pattern - 0.5 * stripeWidth));
// 混合顏色
diffuseColor.rgb = mix(diffuseColor.rgb, stripeColor, isStripe * smoothFactor);
`
)
}
material.defines = { 'USE_UV': "" }
let mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
let clock = new THREE.Clock()
function animation() {
const delta = clock.getDelta();
// 計(jì)算歸一化的流動(dòng)速度 (速度系數(shù) / 管線長(zhǎng)度)
const normalizedSpeed = uniforms.speedFactor.value / uniforms.totalLength.value*10;
// 更新條紋偏移量
uniforms.stripeOffset.value += delta * normalizedSpeed;
// 重置偏移量以保持循環(huán)
uniforms.stripeOffset.value = uniforms.stripeOffset.value % 1.0;
requestAnimationFrame(animation)
}
animation()
}
const points = [[-1137,70,2850],[694,70,2810],[684,70,1550]]
createPipeLine(THREE,scene,points)因?yàn)槲壹虞d了一個(gè)模型地板比較大,所以我把管線相關(guān)的參數(shù)設(shè)置的也比較大。
實(shí)現(xiàn)效果如下:

其實(shí)沒有達(dá)到我的預(yù)期,我想的是如下箭頭效果,但這個(gè)曲線它自動(dòng)偏移圓滑了一些。

于是我換一個(gè)曲線API,使用LineCurve3去實(shí)現(xiàn)管線。

效果如下,管線畫出來了,但是拐角又不是很好看。解決辦法就是把拐角的點(diǎn)設(shè)置多一些,就看起來比較圓滑了。

拐角點(diǎn)設(shè)置多后的效果

代碼如下
/**
* 繪制管線
* @param THREE
* @param scene
* @param points
*/
function createPipeLine(THREE, scene ,points) {
const cps = points.map(item=>{
return new THREE.Vector3(...item)
})
console.log(cps,'cpscps')
const curve = new THREE.CurvePath();
for (let i = 0; i < cps.length - 1; i++) {
// 每?jī)蓚€(gè)點(diǎn)之間形成一條三維直線
const lineCurve = new THREE.LineCurve3(cps[i], cps[i + 1]);
// curvePath有一個(gè)curves屬性,里面存放組成該三維路徑的各個(gè)子路徑
curve.curves.push(lineCurve);
}
let geometry = new THREE.TubeGeometry(curve, 100, 10, 32)
const material = new THREE.MeshLambertMaterial({
color: '#19bbd5',
side: THREE.DoubleSide,
})
let length = curve.getLength()
let uniforms = {
totalLength: { value: length },
stripeOffset: { value: 0 }, // 條紋偏移量
stripeWidth: { value: 100 }, // 條紋寬度 (歸一化值)
stripeSpacing: { value: 100 }, // 條紋間距 (歸一化值)
stripeColor: { value: new THREE.Color('#096be3') }, // 條紋顏色
speedFactor: { value: 50 },
};
material.onBeforeCompile = shader => {
shader.uniforms.totalLength = uniforms.totalLength;
shader.uniforms.stripeOffset = uniforms.stripeOffset;
shader.uniforms.stripeWidth = uniforms.stripeWidth;
shader.uniforms.stripeSpacing = uniforms.stripeSpacing;
shader.uniforms.stripeColor = uniforms.stripeColor;
shader.fragmentShader = `
uniform float totalLength;
uniform float stripeOffset;
uniform float stripeWidth;
uniform float stripeSpacing;
uniform vec3 stripeColor;
${shader.fragmentShader}
`.replace(
`#include <color_fragment>`,
`#include <color_fragment>
// 計(jì)算條紋模式
float pattern = mod((vUv.x - stripeOffset) * totalLength / (stripeWidth + stripeSpacing), 1.0);
float isStripe = step(pattern, stripeWidth / (stripeWidth + stripeSpacing));
// 平滑邊緣
float edge = fwidth(vUv.x) * 2.0;
float smoothFactor = smoothstep(0.0, edge, abs(pattern - 0.5 * stripeWidth));
// 混合顏色
diffuseColor.rgb = mix(diffuseColor.rgb, stripeColor, isStripe * smoothFactor);
`
)
}
material.defines = { 'USE_UV': "" }
let mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
let clock = new THREE.Clock()
function animation() {
const delta = clock.getDelta();
// 計(jì)算歸一化的流動(dòng)速度 (速度系數(shù) / 管線長(zhǎng)度)
const normalizedSpeed = uniforms.speedFactor.value / uniforms.totalLength.value*10;
// 更新條紋偏移量
uniforms.stripeOffset.value += delta * normalizedSpeed;
// 重置偏移量以保持循環(huán)
uniforms.stripeOffset.value = uniforms.stripeOffset.value % 1.0;
requestAnimationFrame(animation)
}
animation()
}
const points = [[-1137,70,2850],[654, 70, 2850], [664, 70, 2840], [674, 70, 2830],[684,70,2820],[694,70,2810],[684,70,1550]]
createPipeLine(THREE,scene,points)上面的方案能實(shí)現(xiàn)流動(dòng)的管線,但是拐角的點(diǎn)位很難處理,還是打算再換一種方案。
最終方案:直線使用LineCurve3+拐角曲線CatmullRomCurve3組合使用。
效果如下

源代碼
/**
* 繪制管線
* @param THREE
* @param scene
* @param points
*/
function createPipeLine(THREE, scene ,points) {
if(points.length<=1){
return false
}
const curvePath = new THREE.CurvePath();
let prevPoint = null; // 用于記錄上一個(gè)點(diǎn)
// 遍歷點(diǎn)數(shù)組
for (let i = 0; i < points.length; i++) {
const current = points[i];
// 處理普通點(diǎn)(一維數(shù)組)
if (Array.isArray(current) && current.length === 3 && typeof current[0] === 'number') {
const point = new THREE.Vector3(...current);
if (prevPoint === null) {
// 第一個(gè)點(diǎn),只記錄不創(chuàng)建曲線
prevPoint = point;
} else {
// 創(chuàng)建直線連接到當(dāng)前點(diǎn)
curvePath.add(new THREE.LineCurve3(prevPoint, point));
prevPoint = point;
}
}
// 處理拐角點(diǎn)(二維數(shù)組)
else if (Array.isArray(current) && current.length >= 3) {
// 將拐角點(diǎn)數(shù)組轉(zhuǎn)換為Vector3
const curvePoints = current.map(p => new THREE.Vector3(...p));
// 確保與上一個(gè)點(diǎn)連接
if (prevPoint !== null) {
// 檢查拐角曲線的起點(diǎn)是否與上一個(gè)點(diǎn)相同
if (!prevPoint.equals(curvePoints[0])) {
// 如果不相同,添加一條連接線
curvePath.add(new THREE.LineCurve3(prevPoint, curvePoints[0]));
}
}
// 創(chuàng)建CatmullRom曲線
const cornerCurve = new THREE.CatmullRomCurve3(curvePoints);
curvePath.add(cornerCurve);
// 更新上一個(gè)點(diǎn)為拐角曲線的最后一個(gè)點(diǎn)
prevPoint = curvePoints[curvePoints.length - 1];
} else {
console.warn('無效的點(diǎn)格式:', current);
}
}
// 創(chuàng)建管道幾何體
const segments = Math.max(100, Math.floor(curvePath.getLength() / 20));
const geometry = new THREE.TubeGeometry(
curvePath,
segments, // 動(dòng)態(tài)計(jì)算分段數(shù)
10, // 管道半徑
16, // 徑向分段數(shù)
false // 是否閉合
);
const material = new THREE.MeshLambertMaterial({
color: '#19bbd5',
side: THREE.DoubleSide,
})
let length = curvePath.getLength()
let uniforms = {
totalLength: { value: length },
stripeOffset: { value: 0 }, // 條紋偏移量
stripeWidth: { value: 100 }, // 條紋寬度 (歸一化值)
stripeSpacing: { value: 100 }, // 條紋間距 (歸一化值)
stripeColor: { value: new THREE.Color('#096be3') }, // 條紋顏色
speedFactor: { value: 50 },
};
material.onBeforeCompile = shader => {
shader.uniforms.totalLength = uniforms.totalLength;
shader.uniforms.stripeOffset = uniforms.stripeOffset;
shader.uniforms.stripeWidth = uniforms.stripeWidth;
shader.uniforms.stripeSpacing = uniforms.stripeSpacing;
shader.uniforms.stripeColor = uniforms.stripeColor;
shader.fragmentShader = `
uniform float totalLength;
uniform float stripeOffset;
uniform float stripeWidth;
uniform float stripeSpacing;
uniform vec3 stripeColor;
${shader.fragmentShader}
`.replace(
`#include <color_fragment>`,
`#include <color_fragment>
// 計(jì)算條紋模式
float pattern = mod((vUv.x - stripeOffset) * totalLength / (stripeWidth + stripeSpacing), 1.0);
float isStripe = step(pattern, stripeWidth / (stripeWidth + stripeSpacing));
// 平滑邊緣
float edge = fwidth(vUv.x) * 2.0;
float smoothFactor = smoothstep(0.0, edge, abs(pattern - 0.5 * stripeWidth));
// 混合顏色
diffuseColor.rgb = mix(diffuseColor.rgb, stripeColor, isStripe * smoothFactor);
`
)
}
material.defines = { 'USE_UV': "" }
let mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
let clock = new THREE.Clock()
function animation() {
const delta = clock.getDelta();
// 計(jì)算歸一化的流動(dòng)速度 (速度系數(shù) / 管線長(zhǎng)度)
const normalizedSpeed = uniforms.speedFactor.value / uniforms.totalLength.value*10;
// 更新條紋偏移量
uniforms.stripeOffset.value += delta * normalizedSpeed;
// 重置偏移量以保持循環(huán)
uniforms.stripeOffset.value = uniforms.stripeOffset.value % 1.0;
requestAnimationFrame(animation)
}
animation()
}
const points = [
[-1137,70,2850],
[440,70,2850],
[[470,70,2838],[480,70,2825],[490,70,2812]],//拐角點(diǎn)二維數(shù)組且至少三個(gè)點(diǎn)
[500,70,2800],
[500,70,1850]
]
createPipeLine(THREE,scene,points)總結(jié)
到此這篇關(guān)于Three.js中實(shí)現(xiàn)流動(dòng)的管線效果的文章就介紹到這了,更多相關(guān)Three.js流動(dòng)管線效果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript笛卡爾積算法實(shí)現(xiàn)方法
這篇文章主要介紹了javascript笛卡爾積算法實(shí)現(xiàn)方法,實(shí)例分析了笛卡爾積算法的javascript實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04
JS獲取月份最后天數(shù)、最大天數(shù)與某日周數(shù)的方法
這篇文章主要介紹了JS獲取月份最后天數(shù)、最大天數(shù)與某日周數(shù)的方法,涉及JavaScript針對(duì)日期與實(shí)現(xiàn)的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12
JavaScript點(diǎn)擊按鈕生成4位隨機(jī)驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了JavaScript點(diǎn)擊按鈕生成4位隨機(jī)驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01
一文帶你搞懂JavaScript中的進(jìn)制與進(jìn)制轉(zhuǎn)換
JavaScript 中提供的進(jìn)制表示方法有四種:十進(jìn)制、二進(jìn)制、十六進(jìn)制、八進(jìn)制。本文主要講介紹一下JS中這些進(jìn)制的互相轉(zhuǎn)換,感興趣的可以了解一下2023-02-02
手寫Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash
這篇文章主要介紹了手寫Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash,接下來將會(huì)帶你們了解下這兩者的區(qū)別,以及我們?cè)撊绾问謱憣?shí)現(xiàn)這兩個(gè)函數(shù)2022-03-03
JS組件Form表單驗(yàn)證神器BootstrapValidator
做Web開發(fā)的我們,表單驗(yàn)證是再常見不過的需求了。友好的錯(cuò)誤提示能增加用戶體驗(yàn)。今天就來看看bootstrapvalidator如何使用,感興趣的小伙伴們可以參考一下2016-01-01
JavaScrip如何安全使用Payment Request API詳解
這篇文章主要為大家介紹了JavaScrip如何安全使用Payment Request API詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10

