行歌梦想家 https://www.xingar.com Mon, 30 Jun 2025 11:50:35 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.8 https://cdn.xingar.com/wp-content/uploads/2025/04/cropped-ChatGPT-Image-Apr-29-2025-08_19_24-AM-32x32.png 行歌梦想家 https://www.xingar.com 32 32 目前最强开源图像编辑模型flux kontext https://www.xingar.com/flux-kontext-open-source/ Mon, 30 Jun 2025 07:50:26 +0000 https://www.xingar.com/?p=626 就在上个礼拜黑森林实验室开源了flux kontext的dev版模型,该模型是目前为止最强的图像编辑模型直接对标GPT-4o,这两天迫不及待的试了下,效果是针不戳,工作流也特别简单,基本只需要提示词就能玩,可以和以往的复杂工作流说拜拜啦。

下面行歌会从风格转绘,产品试穿,扩图,人物移除,角色融合等角度展示一下kontext dev的能力,相信我它觉得可以成为你今后工作中的利器。

Kontext基础工作流

首先我们要把comfyui升级到最新版本,并安装好相关的模型,行歌使用的是onthingai在线云平台,可以0配置使用kontext工作流。

进入实例在左上角的工作流(workflow)菜单里,官方已经贴心的为我们准备好了kontext的工作流的两个模板,基本上用一个基础模板就能实现我们这次的所有效果,选择dev的fp16版本,开始我们的生图之旅。

风格转绘

前面提到过Kontext的工作流使用起来非常简单,基本上只用提示词就能实现想要的效果,我们上传一张“带珍珠项链的女孩”图片,通过下面的提示词模板就能转换成任意风格,还不快把玩起来。

transform [主体] to [风格] style,keep the composition unchanged

提示词的意思是把主体转换成我们自定义的风格并保持构图不变,我们也可以根据自己的需要去掉保持构图风格的提示词。

[风格]可以替换成Ghibli(吉卜力),PixelArt(像素艺术),cyberPunk(赛博朋克),steampunk(蒸汽朋克)等关键字,是不是很好玩。

物品迁移

在产品迁移的场景里我们需要上传一张人物图片和一张产品图,这里我上传了一张旗袍美女和一个中国风的包包,图片都是由即梦AI生成的。

我们只需要简单输入下面的提示词,Kontext就为我们生成了很不错的产品试用试穿的效果。

the girl is hoding a bag

可以看到人物的外观和产品细节都保持得相当不错,下面我们来试一张带中文文字的包包,看看kontext能不能很好的保持产品的文字细节。

完全没有难住kontext,包包上的文字被很好的迁移了,kontext的能力真是让人惊艳。

人物移除

没什么好说的,只需要在提示词中描述我们想要移除的物体就可以了。

remove the character in the middle, keep the composition unchanged

我们可以看到人物和她在荷叶上的阴影都被很好的移除了,但水中的倒影还在,大家可以修改提示词和多抽卡就可以得到完全移除的效果。

扩图

扩图也很简单,只要调整好输入latent图片大小,然后输入以下关键字就能达到扩图的效果。

inpainting the image

这里发现扩图以后图片的明暗有了一些变化,大家可以多抽卡。然后我们还可以把背景换一换。

change the background to a beach scene

角色融合

我们来上传两张角色图片,把他们融合在一个场景里,我们来让珍珠项链女孩抱着这个labubu玩偶。

replace the two characters in one scene where the girl is hugging the toy

然后我们我们还可以继续编辑图片,通过提示词来改变图片风格,这就是该工作流的连续编辑功能,释放图像创意的无限可能性,让人欲罢不能 ,完全停不下来。

总结

AI图像技术的发展让我们的学习成本越来低,以往复杂工作流才能完成的任务现在只需要简单的自然语言就能完成,这也是技术发展的必然趋势。

]]>
大模型微调实现一个医美诊断APP https://www.xingar.com/llm-fine-tuning-app/ Fri, 27 Jun 2025 01:10:09 +0000 https://www.xingar.com/?p=579 最近收到一个小伙伴的需求,希望可以根据用户自拍上传的颈部图片,自动识别出用户颈纹的严重程度并给出评分等级。针对这个需求我们基本上可以用图文理解大模型来解决,但是由于没有在这方面进行过专门的训练,大模型在识别时容易出现幻觉,不能很好的对标我们自己的等级评分标准。

我们现在百度智能云平台选择一款多模态大模型进行试验,先在百度智能云平台选择一款多模态大模型进行试验,这里我们选择通义千问2.5vl模型来测试它的图文理解能力。

你是一名专业的颈纹分析师,根据用户上传的图片来给颈纹分等级,颈纹的等级分为1-5级,数字越大颈纹越严重,对比颈纹分级标准,1级几乎无颈纹,皮肤光滑,2级轻微的细小颈纹,仅在特定角度或表情下可见,3级颈纹较为明显,但深度和宽度较浅,4级颈纹较深且宽,分布广泛,5级颈纹非常深且宽,皮肤明显松弛

在对大模型进行了角色设定后,我们上传了一张颈部纹理比较严重的图片,大模型给出了等级为3的结果,这跟我们自己的评分标准出入比较大,这个用户颈纹严重程度在我们的标准里应该是最高等级5。

为了解决这个问题,我们可以利用百度智能云平台的模型训练能力对这个大模型进行微调,使它更符合我们的要求。

准备训练数据集

在进行模型精调之前先要准备好用户精调的数据集,数据集是一系列的对话数据,大模型根据对话数据进行微调使输出更符合我们的预期。

这里我在网上搜集了100张颈纹图片对应100条对话数据(平台要求训练数据不能少于100条),我们现在文本文档里对这100条数据进行简单的评分和描述,后面再用工具将数据处理成平台要求的训练数据格式。

这张图片里的颈纹很明显,褶皱比较多,可以归为5级 01.png
这张图片里的颈纹比较明显,褶皱略多,可以归为4级 02.png
这张图片里的颈纹很明显,褶皱比较多,可以归为5级 03.png
这张图片里的颈纹不太明显,可以归为2级 04.png
这张图片里的颈纹几乎看不到,颈部皮肤很光滑,可以归为1级 05.png
...

然后我们可以用python或者nodejs这种可以操作文件的脚本工具把数据处理成平台要求的训练数据格式,这步可以让cursor帮你生成,这里我生成了一个由100条对话组成的json数据文件。

{"images":["images/01.png"],"messages":[{"role":"system","content":"你是一名专业的颈纹分析师,根据用户上传的图片来给颈纹分等级,颈纹的等级分为1-5级,数字越大颈纹越严重,对比颈纹分级标准,1级几乎无颈纹,皮肤光滑,2级轻微的细小颈纹,仅在特定角度或表情下可见,3级颈纹较为明显,但深度和宽度较浅,4级颈纹较深且宽,分布广泛,5级颈纹非常深且宽,皮肤明显松弛"},{"role":"user","content":"请给图片<ImageHere>中的颈纹分等级"},{"role":"assistant","content":"这张图片里的颈纹很明显,褶皱比较多,可以归为5级"}]}
{"images":["images/02.png"],"messages":[{"role":"user","content":"请给图片<ImageHere>中的颈纹分等级"},{"role":"assistant","content":"这张图片里的颈纹比较明显,褶皱略多,可以归为4级"}]}
{"images":["images/03.png"],"messages":[{"role":"user","content":"请给图片<ImageHere>中的颈纹分等级"},{"role":"assistant","content":"这张图片里的颈纹很明显,褶皱比较多,可以归为5级"}]}
{"images":["images/04.png"],"messages":[{"role":"user","content":"请给图片<ImageHere>中的颈纹分等级"},{"role":"assistant","content":"这张图片里的颈纹不太明显,可以归为2级"}]}
{"images":["images/05.png"],"messages":[{"role":"user","content":"请给图片<ImageHere>中的颈纹分等级"},{"role":"assistant","content":"这张图片里的颈纹几乎看不到,颈部皮肤很光滑,可以归为1级"}]}

上传数据集开始训练

在模型精调里先选择我们之前测试过的通义千问大模型2.5vl-7B。

因为我们的数据集比较少,为了得到更好的效果,我们可以把迭代次数设置为5,并采用全量更新的方式,然后选择我们之前训练好的数据集。

部署和检验

大概过了40分钟我们的数据就训练好了,然后将我们训练好的数据进行部署。

图像理解模型的部署和体验的费用对于个人来说还是比较贵的,相信随着大模型的飞速发展这个价格后面也会降下来,我们也可以尝试选择deepseek的多模态版本进行微调和私有化部署。

最后我们在线测试一下模型微调以后的效果,可以看到模型微调后给出的等级评分基本上和我们的标准保持一致或者接近。

最后我们可以将发布好的大模型,提供外部接口给我们的小程序,APP或者H5来调用。

]]>
webGPU渲染闪电带你渡劫飞升 https://www.xingar.com/webgpu-lightning-effect/ Fri, 13 Jun 2025 07:46:11 +0000 https://www.xingar.com/?p=534

这次我们介绍如何在three.js中用webGPU渲染这种闪电渡劫飞升效果,实现这种效果的方法也有很多,这里只是抛砖引玉介绍行歌这两天实践出的一种方法,如果你有更好的实现方式,欢迎在留言区分享。

实现闪电效果可以分为以下几步:创建闪电几何体,创建闪电发光效果,闪电动画。

创建闪电几何体

首先我们沿Y轴方向创建一些列的点来构成闪电的基本形状,我们后面可以在TSL中将这些点转换成面。

const points = [];
const pointsCount = 15;
const height = 15;
const interY = height / (pointsCount - 1);

for (let i = 0; i < pointsCount; i++) {
  const tx = (Math.random() - 0.5) * 1;
  const ty = i * interY;
  const tz = (Math.random() - 0.5) * 1;
  const point = new Vector3(tx, ty, tz);

  points.push(point);
}
const geometry = new LineGeometry(points);

这里使用了自定义的LineGeometry类,方便我们后面通过点来实现面。

export default class LineGeometry extends BufferGeometry {
  
  constructor(points: Vector3[]) {
    super();

    const count = points.length;
    const positions = new Float32Array(count * 3 * 2);
    const indices = new Uint16Array((count - 1) * 2 * 3);
    const ratios = new Float32Array(count);

    for (let i = 0; i < count; i++) {
      const i1 = i * 1;
      const i2 = i * 2;
      const i6 = i * 6;

      const point = points[i];

      //position
      positions[i6 + 0] = point.x;
      positions[i6 + 1] = point.y;
      positions[i6 + 2] = point.z;

      positions[i6 + 3] = point.x;
      positions[i6 + 4] = point.y;
      positions[i6 + 5] = point.z;

      indices[i6 + 0] = i2 + 2;
      indices[i6 + 1] = i2;
      indices[i6 + 2] = i2 + 1;

      indices[i6 + 3] = i2 + 1;
      indices[i6 + 4] = i2 + 3;
      indices[i6 + 5] = i2 + 2;

      ratios[i1] = i / count;
    }

    this.setAttribute("position", new Float32BufferAttribute(positions, 3));
    this.setAttribute("ratio", new Float32BufferAttribute(ratios, 1));
    this.setIndex(new BufferAttribute(indices, 1));
  }
}

借助一些TSL技巧我们用一系列的分段平面几何体来代表闪电,借助一些shader技巧我们可以让这些平面几何体始终朝向摄像机的方向。

material.vertexNode = Fn(() => {
  const worldPosition = modelWorldMatrix.mul(vec4(positionGeometry, 1));
  const toCamera = worldPosition.xyz.sub(cameraPosition).normalize();

  const nextPosition = positionGeometry.add(vec3(0, 1, 0));
  const nextWorldPosition = modelWorldMatrix.mul(vec4(nextPosition, 1);
  const nextDelta = nextWorldPosition.xyz.sub(worldPosition.xyz).normalize();
  const tagent = cross(nextDelta, toCamera).normalize();

  const sideStep = floor(float(vertexIndex).mul(3).sub(2).div(3).mod(2)).sub(
    0.5
  );
  const sideOffset = tagent.mul(sideStep.mul(0.3));

  worldPosition.addAssign(vec4(sideOffset, 0));

  const viewPosition = cameraViewMatrix.mul(worldPosition);
  return cameraProjectionMatrix.mul(viewPosition);
})();

借助我们的老朋友vertexIndex我们可以在TSL中让我们的几何体有粗细的变化,看起来更像闪电了。

const ratio = attribute("ratio");
const baseThickness = ratio
  .sub(0.5)
  .abs()
  .mul(2)
  .oneMinus()
  .smoothstep(0, 1);
const remapProgress = progress.mul(3).sub(1);
const progressThickness = ratio.sub(
  remapProgress.abs().oneMinus().smoothstep(0, 1)
);
const finalThickness = mul(thickness, baseThickness, progressThickness);

const sideStep = floor(float(vertexIndex).mul(3).sub(2).div(3).mod(2)).sub(
  0.5
);
const sideOffset = tagent.mul(sideStep.mul(finalThickness));

闪电发光效果

我们给渲染加入后期bloom发光效果,在three.js中实现这样的效果很简单,只需要引入postprocessing库。

 const postProcessing = new PostProcessing(renderer);
 const scenePass = pass(scene, camera);
 const scenePassColor = scenePass.getTextureNode("output");
 bloomPass = bloom(scenePassColor);
 bloomPass.threshold.value = 0;
 bloomPass.strength.value = 2;
 bloomPass.radius.value = 0.7;
 postProcessing.outputNode = scenePassColor.add(bloomPass);

闪电动画

闪电动画我是使用两组顶点之间做过渡来实现,第一组顶点构成了闪电开始时的形状,第二组顶点构成结束时的形状,让闪电在两种形状间做过渡就形成了闪电出现时的效果。

_update() {
  const position = this.geometry.getAttribute("position") as BufferAttribute;
  for (let i = 0; i < this.pointsCount; i++) {
    const point = this.startPoints[i].lerp(
      this.endPoints[i],
      this.shapeProgress
    );

    position.setXYZ(i * 2, point.x, point.y, point.z);
    position.setXYZ(i * 2 + 1, point.x, point.y, point.z);
  }
  position.needsUpdate = true;
}

用gsap库很容易就实现了我们的动画效果。

gsap.to(this, {
  shapeProgress: 1,
  duration: 0.3,
  ease: "power2.Out",
  delay: 0.1,
  onUpdate: () => {
    this._update();
  },
});

我们可以用gsap改变闪电的thickness参数来实现闪电的消失动画。

gsap.to(this.thickness, {
  value: 0,
  duration: 0.5,
  ease: "power2.inOut",
  delay: 0.5,
  onComplete: () => {
    this.destroy();
  },
});
]]>
webGPU渲染任意形状的流水地形 https://www.xingar.com/webgpu-water-terrain/ Wed, 04 Jun 2025 05:14:09 +0000 https://www.xingar.com/?p=479

在上一篇文章《webGPU高效渲染任意形状草地》中,我们介绍了如何用一张自定义颜色贴图来生成任何形状的草地;使用类似的技巧再结合blender建模工具,我们还可以生成河床洼地等任意形状的流水地貌,再结合rapier这样的物理引擎就可以直接做出一个小小的3D游戏世界。

用颜色贴图生成任意流水地貌

与上次不同的是,这次我们在blender中绘制河床和草地的颜色贴图,blender中的材质绘制模式(Texture Paint)可以很好的完成这项工作,选择合适的笔刷和笔触让地形的边缘有些过渡效果。

新建一个平面模型对象,然后用我们刚才绘制好的颜色贴图对平面做变形处理,生成水床洼地的凹陷地形;这个我们可以借助blender中的geometry Nodes来轻松实现。

Blender 中的 ​​Geometry Nodes(几何节点)​​ 是一个基于节点编辑的建模与程序化生成工具,它允许用户通过连接节点来创建复杂的几何体操作和动画效果,无需编写代码即可实现参数化设计.

可以看到我们用贴图中的蓝色通道对模型进行了形变处理,生成了凹陷地形。

this.terrainColorNode = Fn(([]: any) => {
  const terrainUv = this.terrainUvNode(positionWorld.xz);
  const terrainData = this.terrainDataNode(terrainUv);
  // 地面颜色
  const baseColor = color(this.dirtColorUniform).toVar();
  // 草地颜色
  baseColor.assign(mix(baseColor, this.grassColorUniform, terrainData.g));
  // 流水颜色
  baseColor.assign(
    mix(
      baseColor,
      this.waterSurfaceColorUniform,
      smoothstep(0, 0.3, terrainData.b)
    )
  );
  baseColor.assign(
    mix(
      baseColor,
      this.waterDepthColorUniform,
      smoothstep(0.3, 1, terrainData.b)
    )
  );
  return baseColor.rgb;
});

借助一些TSL技巧我们就可以用颜色通道贴图在three.js中生成流水地形。

生成水面波纹效果

还是利用刚才的颜色通道贴图再加上些shader技巧,我们可以用TSL生成唯美的水面波纹效果,当然这个不是物理真实的效果,只是简单的模拟,但对于我的需求来说已经足够了。

this.waterMaterial.outputNode = Fn(() => {
  const terrainUv = this.terrainUvNode(positionWorld.xz);
  const terrainData = this.terrainDataNode(terrainUv);
  const noise = texture(this.noiseTexture, positionWorld.xz.mul(0.1)).r;
  const ripple = terrainData.b
    .add(time.mul(0.1))
    .mul(10)
    .mod(1)
    .sub(terrainData.b.oneMinus())
    .add(noise);
  ripple.greaterThan(0.01).discard();
  return vec4(vec3(1), 1);
})();

通过对颜色贴图采样添加噪声,我们得到了一个不错的水波动画效果。

水痕和物理引擎

角色和地形的碰撞物理引擎使用的是rapier.js,使用起来也非常方便,该引擎已经内置到了three.js核心库中,大家可以参考官方的案例,这里就不过多的介绍了。

最后为了让场景更丰富些,我加入了之前介绍过的草地和天空背景。

当角色走入河床低洼处,人物的部分身体是处于水面之下的,我们可以通过TSL为人物添加水痕的效果让水面的效果更逼真些。

character.material.colorNode = Fn(([]: any) => {
  const baseColor = color(child.material.color).toVar();
  const waterMix = positionWorld.y
    .remapClamp(
      this.waterThreshold,
      this.waterThreshold.sub(this.waterAmplitude),
      1,
      0
    )
    .mul(positionWorld.y.step(this.waterThreshold))
    .pow(this.waterPower);
  baseColor.assign(mix(baseColor, color("#ffffff"), waterMix));
  return baseColor;
})();

总结

用好颜色通道贴图和blender建模工具可以极大的提高我们3D开发的效率,它们可以方便用来进行场景搭建,关卡设计等任务,还不赶快实践起来!

]]>
webGPU高效渲染任意形状草地 https://www.xingar.com/webgpu-shape-grass/ Tue, 27 May 2025 14:22:05 +0000 https://www.xingar.com/?p=445

书接上回webGPU实现游戏中的绿植和随风摆动效果,今天我们来看一看如何实现3D游戏中常见的草地效果,并且让这些草可以按照我们的需要形成任意的形状,甚至可以根据地形的起伏来生成。

生成大量的草地

要生成大量的草地又要考虑渲染性能,首先想到的是用instanceMesh来实现,网上也有一些通过该方法实现的教程,但比instanceMesh更高效的方法是只用一个Mesh来实现。

我们使用BufferGeometry来生成大量的三角形顶点来模拟草叶,先随机生成三角形的中心点,然后通过中心点找该三角形的三个顶点。之所以需要中心点的位置,是因为方便我们后面在TSL里旋转该三角形,同时也方便我们生成任意形状的草地,后面你会看到这样做的好处。

const positions = new Float32Array(count * 3 * 3);
const centers = new Float32Array(count * 3 * 2);

for (let i = 0; i < count; i++) {

  const bw = bladeW + bladeW * randFloat(0, bladeWRandomness);
  const bh = bladeH + bladeH * randFloat(0, bladeHRandomness);
  const bfw = bw * 0.5;

  positions[stride9 + 0] = -bfw;
  positions[stride9 + 1] = 0;
  positions[stride9 + 2] = 0;

  positions[stride9 + 3] = 0;
  positions[stride9 + 4] = bh;
  positions[stride9 + 5] = 0;

  positions[stride9 + 6] = bfw;
  positions[stride9 + 7] = 0;
  positions[stride9 + 8] = 0;

  const maxRadius = randFloat(tileRadius, tileRadius + randFloat(0, 1));
  const centerX = randFloatSpread(maxRadius);
  const centerZ = randFloatSpread(maxRadius);

  centers[stride6 + 0] = centerX;
  centers[stride6 + 1] = centerZ;
  centers[stride6 + 2] = centerX;
  centers[stride6 + 3] = centerZ;
  centers[stride6 + 4] = centerX;
  centers[stride6 + 5] = centerZ;
}

this.geometry = new BufferGeometry();
this.geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
this.geometry.setAttribute("center", new Float32BufferAttribute(centers, 2));

生成任意形状的草地

首先在photoshop中用纯绿色画出你需要的草地形状图形,这里的颜色可以RGB三色中的任意一种,也可以是alpha透明通道。

原理也很简单,首先将草叶顶点的世界坐标转换到这张贴图中的uv坐标,然后根据贴图中的绿色通道的值来移动草叶的位置。

const x = linearstep(-25, 25, center.x);
const y = linearstep(-25, 25, center.y).oneMinus();

const grassTex = texture(this.grassTexture, vec2(x, y));
If(grassTex.g.lessThan(0.1), () => {
  newPos.y.assign(999);
});
]]>
教你用Blender整合角色动画,三步搞定动画合并! https://www.xingar.com/blender-character-animations-integration/ Sat, 24 May 2025 11:27:59 +0000 https://www.xingar.com/?p=377

3D游戏开发中,有时我们需要将3D建模师制作好的一系列角色动画文件,比如:行走动画,奔跑动画,攻击攻击动画等整合到一个文件中,这样使用起来更方便也节省加载流量。作为3D开发人员我们不必精通建模(当然精通更好),但熟悉制作流程就能更好地和建模师配合制作出更出色的作品。

在Mixamo中制作角色动作动画

Mixamo是Adobe旗下的3D角色动画制作工具,提供了大量的角色动作供我们选择,基本上可以满足大部分场景的需求,这次我们要用它来制作角色的行走,奔跑和空闲三种动画。

因为Mixamo只支持fbx格式的模型文件,首先我们在blender中将制作好的角色模型导出为fbx格式文件。

在左上角筛选出“空闲”动画进行选择,选择满意的一个动画下载。

点击下载,在弹框中骨骼蒙皮(skin)这里要选择下载完整的骨骼蒙皮,将下载的fbx文件导入到blender中。

同理挑选出自己满意的行走和奔跑动画也下载下来,这里要注意的是不要选择下载蒙皮,因为我们只需要一套蒙皮。

在Blender中合并角色动画

在blender中导入刚才生成的空闲(idle)动画fbx文件,然后参考图中标识将动画改名为Idle。

同样导入行走动画文件,将动画重命名为Walk。

在左上角的”非线性动画”面板中,点击“向下压入动作”按钮(注意这里确定空闲的骨骼是选中状态)。

点击新建动作。

在动作面板中,选择之前导入并重命名的Walk动画。

至此我们已经完成了空闲和行走动画的合并工作,现在可以安全的删除之前导入的行走骨骼。

用同样的方法导入角色的奔跑动画。

导出GLB一键瘦身

最后我们将文件导出为glb格式,并勾选draco压缩选项,导出后的glb文件减少到了原来三个fbx文件总大小的六分之一。

🎉总结一下:

Mixamo负责动作,Blender负责整合;一套皮肤多套动作,文件不重模型不乱;GLB导出配合Draco压缩,轻松优化性能。

]]>
《Three.js 3D游戏开发系列》第一人称摄像机FPS https://www.xingar.com/threejs-fps-camera/ Wed, 21 May 2025 14:48:25 +0000 https://www.xingar.com/?p=362

准备更新一个Three.js 3D游戏开发系列视频教程发布在B站,总结提炼一些web 3D游戏开发中的一些经验和技巧,案例代码都会使用three.js的webGPU渲染,这次我们来实现一个最简版本的第一人称摄像机。

]]>
用coze搭建一个微公众号AI智能体助手 https://www.xingar.com/coze-wechat-ai-agent/ Mon, 19 May 2025 08:19:05 +0000 https://www.xingar.com/?p=285 最近有一个想法,想把自己公众号的文章整理成一个知识库,方便自己和用户对文章中的内容进行检索,可以快速的查找到相关的知识和对应的文章链接,于是便有了搭建一个智能体Agent助手的想法。

搭建这样的AI智能体基本上有两种方案,它们各有优缺点可以根据自己的需求来选择:

1. 私有化服务器或者本地部署:可以自己选择开源的LLM大模型和RAG(比如DeepSeek+RagFlow的组合),系统在服务器上安装搭建所需环境,优点是私密性比较好;缺点是需要服务器和带宽成本,部署起来虽然不复杂但是比较花时间。

2.使用coze扣子智能体平台:无需编写代码,通过可视化界面就可以迅速搭建出一个智能体,优点是简单易用,不需要花时间去部署服务器;缺点是不适合私密性要求比较高的场景。

我们的智能体没有私密性的需求,因此我们选择简单易用,成本相对更低的扣子方案。

创建扣子智能体Agent

在coze平台,创建智能体“行歌梦想家”点击确认。

在左侧的编排页面输入,人设和回复逻辑,我们先简单的写一下角色的描述,后面再做完善,输入:

你是行歌梦想家公众号助手,回答和行歌梦想家公众号文章有关的问题。

点击右侧的自动优化提示词完成初步的角色编排,得到优化后的提示词如下,可以看到提示词被用Markdown语法完善和优化了,后面我们添加好插件和知识库后可以进一步对这里进行完善和优化。

创建知识库

在中间的知识区域,点击文本后面的加号,添加文本类型的知识库。

输入知识库名称,类型选择“在线数据”,之所以选择“在线数据”类型而没有选择“公众号”类型,是因为目前公众号抓取的接口还不完善,无法抓取到群发文章的内容,好在我们除了公众号之外还有自己的网站,我们可以先暂时抓取网站的数据。

添加我们要采集抓取的网站网址(https://www.xingar.com),添加方式选择批量添加,这样程序就可以自动抓取我们网站的全部内容,点击导入按钮。

导入后会显示抓取到所有网站链接,可以根据自己的需要进行筛选,确认以后就完成了知识库的添加。

添加搜索插件

扣子可以通过一系列的插件或者工作流来扩展智能体的能力,其中工作流的功能非常强大,它可以实现复杂的应用逻辑,还可以单独发布成API共其他程序调用,我们的功能比较简单暂时不会用到。这里我们希望智能体助手在知识库中没有找到用户问题的答案时,可以借助搜索引擎在网上查找答案,点击中间技能中的“插件”。

添加一个必应搜索插件。

调整人设和回复逻辑

为了让我们之前设置的插件和知识库更好的工作,我们需要重写调整一下之前的人设和回复逻辑,可以理解为我们智能体的提示词,增加下面一段描述:先让智能体根据用户的问题在知识库中查找,如果没找到相关内容,再调用必应搜索插件搜索网络内容。

### 技能 3: 智能内容检索
1. 当用户提问时,优先在{行歌梦想家公众号知识库}中查找能回答用户提问的内容。若未找到,再调用{bingWebSerach}获取信息回答用户。在检索过程中要确保信息的准确性和相关性。如果在知识库中查找到相关内容,请把对应的网页链接输出在最后。

添加好之后,我们可以再对提示词进行一次优化,在最右侧的“预览与调试区域”对我们的智能体进行一波测试,效果满意后就可以将智能体发布了。

发布智能体

点击右上角的发布按钮,会弹出要发布的平台列表,我们这里选择微信订阅号平台。我们还可以将智能体发布成API接口在App,h5或者小程序中调用。配置好订阅号的AppId就可以发布成功了,快到公众号中去试试吧!

]]>
Three.js中用TSL实现一个炫酷的粒子场景 https://www.xingar.com/tsl-firework-scene/ Thu, 15 May 2025 13:43:29 +0000 https://www.xingar.com/?p=243

最近一直在关注学习three.js中webGPU渲染方面的内容,虽然元宇宙这波热度已经过去,但我们学习web 3D的热情没有减弱,当初入门的时候元宇宙的概念还没有在国内兴起,完全是凭兴趣在学习,本着不忘初心的精神,我们要继续学习让自己掌握的技术创造更多的可能性。

我还会继续分享web 3D方面最新的技术,主要也是对自己学习过程的一个记录,此外AI方面的内容也是我后面要分享的重点之一;好了书归正传,下面我介绍这次要分享的一个小案例。

这次也是用TSL来实现这个粒子场景中的大部分效果,TSL是Three Shading Language的缩写,一种中间层的渲染器格式转换成webGPU的WGSL代码或者是webGL的GLSL代码,TSL可以大大的减少我们写shader代码的工作量,同时学习起来也很有趣;入门TSL我强烈推荐Christian Helgeson的这篇TSL入门指引,我也会在文末列出一些入门的参考资料。

创建地面反射效果

这个效果的制作方法,我在之前的这篇《webGPU实现车身速度光线流动特效》做过介绍,不同的是我在地面的边缘做了一些模糊过渡处理,让远处的地平线看起来更自然一些。

floorMaterial.opacityNode = float(1).sub(
  smoothstep(0.02, 0.2, distance(uv(), vec2(0.5)))
);

只需一句简单TSL就可以实现地面边缘的过渡效果,是不是很简单。

人物动画

人物的模型是我在blender制作的,导出为fbx格式然后在Adobe Mixamo里导入制作骨骼动画,选一段跳舞的动画导出,Mixamo目前只支持导出fbx格式的动画模型文件,我们可以在blender转换一下导出对web更友好的glb格式,可以选择draco压缩减少模型文件体积大小。

背景环境贴图

为了制作背景环境天空贴图,我先创建了一个球体,然后在球体内面用TSL画渐变色模拟出天空背景效果,这里用到了上中下三种渐变色,具体的颜色可以根据自己的需要来调整。

const colorTop = color(0x0e1c3e);
const colorMiddle = color(0xff7b05);
const colorBottom = color(0x160c2a);

const geometry = new SphereGeometry(60, 32, 32);
const material = new MeshBasicNodeMaterial({
  color: new Color(0xff0000),
  side: BackSide,
});

const middle = float(0.1);
const blendIntensity = 0.06;
const mixColor = mix(colorBottom, colorTop, smoothstep(0.45, 0.55, uv().y));
const blendMiddle = smoothstep(float(0.5).sub(middle), 0.5, uv().y).mul(
  smoothstep(float(0.5).add(middle), 0.5, uv().y).mul(blendIntensity)
);
const finalColopr = mix(mixColor, colorMiddle, blendMiddle);
material.colorNode = finalColopr;
this.mesh = new Mesh(geometry, material);

粒子效果实现

粒子效果我是从wawa sensei的VFX粒子系统获得的灵感,他的代码是基于React Three Fiber的,我用原生+TSL的方式进行了改写,完善一下后面可以分享出来。

参考资料

Three.JS WebGPURenderer by Christian Helgeson

TSL Official announcement and introduction

Bruno Simon TSL Examples SandBox

]]>
webGPU实现车身速度光线流动特效 https://www.xingar.com/webgpu-car-speed-line/ Thu, 01 May 2025 14:10:02 +0000 https://www.xingar.com/?p=208 在three.js中我们使用webGPU渲染,只需要简单的几步就可以实现这种车身速度光线流动的效果。

实现地面倒影效果

地面倒影效果我们可以用three.js中webGPU的reflector来实现,首先先创建一个平面对象,然后对这个对象应用PBR材质,让地面更具质感。

 const textureLoader = new THREE.TextureLoader();
 const floorColor = textureLoader.load("textures/floor/map.jpg");
 floorColor.wrapS = THREE.RepeatWrapping;
 floorColor.wrapT = THREE.RepeatWrapping;
 floorColor.colorSpace = THREE.SRGBColorSpace;

 const floorNormal = textureLoader.load("textures/floor/normal.jpg");
 floorNormal.wrapS = THREE.RepeatWrapping;
 floorNormal.wrapT = THREE.RepeatWrapping;

 const floorMaterial = new THREE.MeshStandardNodeMaterial();
 floor.material = floorMaterial;

然后创建reflector对象,让地面可以显示倒影,在地面已有的材质上叠加倒影贴图。

reflection = reflector({ resolution: 0.8 });
reflection.target.rotateX(-Math.PI / 2);
reflection.uvNode = reflection.uvNode!.add(floorNormalOffset);
scene.add(reflection.target);

floorMaterial.colorNode = texture(floorColor, floorUV)
  .add(reflection.mul(3))
  .sub(0.017);

隧道线条流动效果

实现这个效果我们可以创建一个圆柱体,可以用代码创建或者在blender里创建,在blender里创建的好处是更简单可控一些。创建好后我们可以对这个圆柱体应用一张线条贴图,让贴图在圆柱体上重复显示。

设置好贴图后,我们可以用TSL对贴图生成UV动画,TSL是Three.js Shading Language的缩写,写一次代码就可以自动编译成GLSL或webGPU的WGSL语言,以往实现这种简单顶点动画需要在shader里写许多代码,TSL的出现极大的简化了这个工作量。

 tunnel.material.colorNode = texture(material.map, uv().sub(vec2(time, 0).mul(2)));

设置车身的动态环境贴图

动态环境贴图我们可以用three.js中的CubeCamera,原理是在场景中设置一个虚拟摄像机,这个摄像机可以实时的将场景渲染成一个环境贴图。

const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(1024);
cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);

然后在渲染的每帧中调用CubeCamera的update方法实时渲染场景贴图。

cubeCamera && cubeCamera.update(renderer, scene);  

最后将渲染好的贴图应用到车子的envMap属性上就大功告成啦。

car.traverse((child) => {
  if (child.isMesh) {
    child.material.envMapIntensity = 5;
    child.material.envMap = cubeRenderTarget.texture;
  }
});
]]>