当前位置: 首页 > news >正文

Three.js蒙皮骨骼变化原理 | 逆推蒙皮网格的世界位置

文章目录

        • 关于蒙皮的GPU计算:
        • 源码解析
        • 转换成CPU可执行的代码:
        • 法线部分

蒙皮骨骼的变化是在GPU中进行的 , 所以像获取静态物体一样获取geometry.position是不行的
查看当前版本(r160)的shader

关于蒙皮的GPU计算:
uniform mat4 bindMatrix;uniform mat4 bindMatrixInverse;uniform highp sampler2D boneTexture;mat4 getBoneMatrix( const in float i ) {int size = textureSize( boneTexture, 0 ).x;int j = int( i ) * 4;int x = j % size;int y = j / size;vec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );vec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );vec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );vec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );return mat4( v1, v2, v3, v4 );}------------------------mat4 boneMatX = getBoneMatrix( skinIndex.x );mat4 boneMatY = getBoneMatrix( skinIndex.y );mat4 boneMatZ = getBoneMatrix( skinIndex.z );mat4 boneMatW = getBoneMatrix( skinIndex.w );------------------------vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );vec4 skinned = vec4( 0.0 );skinned += boneMatX * skinVertex * skinWeight.x;skinned += boneMatY * skinVertex * skinWeight.y;skinned += boneMatZ * skinVertex * skinWeight.z;skinned += boneMatW * skinVertex * skinWeight.w;transformed = ( bindMatrixInverse * skinned ).xyz;-----------法线相关-------------mat4 skinMatrix = mat4( 0.0 );skinMatrix += skinWeight.x * boneMatX;skinMatrix += skinWeight.y * boneMatY;skinMatrix += skinWeight.z * boneMatZ;skinMatrix += skinWeight.w * boneMatW;skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;#ifdef USE_TANGENTobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;#endif

其中需要留意的boneTexture是自动创建的 (如果没提供的话) 按照没使用的情况来算(也没用过) 会自动调用computeBoneTexture

if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );

在这里插入图片描述

	computeBoneTexture() {// layout (1 matrix = 4 pixels)//      RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)//  with  8x8  pixel texture max   16 bones * 4 pixels =  (8 * 8)//       16x16 pixel texture max   64 bones * 4 pixels = (16 * 16)//       32x32 pixel texture max  256 bones * 4 pixels = (32 * 32)//       64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrixsize = Math.ceil( size / 4 ) * 4;size = Math.max( size, 4 );const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixelboneMatrices.set( this.boneMatrices ); // copy current valuesconst boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );boneTexture.needsUpdate = true;this.boneMatrices = boneMatrices;this.boneTexture = boneTexture;return this;}
源码解析

three.js将bone的信息保存在boneTexture上
每个bone的矩阵信息保存在4个像素上 每一个像素存储rgba 4个数据 4个像素加一起就是16个数据 也就是4维矩阵的数据

Skeleton.js update方法
每次更新更新boneMatrices数据,也就是boneTexture的数据,shader中通过这张纹理获取每个点的变化,然后应用到transformed上,也是因此 点的位置CPU获取不到无法获取box3模型以及射线检测等工作。不过使用本章的代码可以获取当前的世界位置,只不过应该会很卡顿,因为便利了模型的所有点,这也是为什么蒙皮骨骼要在GPU中工作。

for ( let i = 0, il = bones.length; i < il; i ++ ) {// compute the offset between the current and the original transformconst matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix;_offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );_offsetMatrix.toArray( boneMatrices, i * 16 );}if ( boneTexture !== null ) {boneTexture.needsUpdate = true;}
转换成CPU可执行的代码:

获取 boneTexture
boneTexture 是自动创建的 但是不是立刻 所以加载完模型是没有的 要获取要在下一帧 渲染过一次后

boneTexture
在这里插入图片描述
shader中的textureFetch读取数据 在CPU中可通过texture.source.data.data获取

实现效果,右侧为生成的mesh。发现法线数据没有正确设置 接下来设置法线
在这里插入图片描述

当前已经获取到位置的代码:

		const SkinnedMeshArr = this.helper.scene.getObjectsByProperty("type","SkinnedMesh") as THREE.SkinnedMesh[];const group = new THREE.Group();group.position.z -= 3;group.position.x -= 3;this.helper.add(group);requestAnimationFrame(() => {SkinnedMeshArr.forEach((skinnedMesh) => {const cloneTarget = new THREE.Mesh(skinnedMesh.geometry.clone(),skinnedMesh.material && !Array.isArray(skinnedMesh.material)? skinnedMesh.material.clone(): undefined);const position = skinnedMesh.geometry.getAttribute("position");const cloneTargetPosition =cloneTarget.geometry.getAttribute("position");const skinIndex =skinnedMesh.geometry.getAttribute("skinIndex");const skinWeight =skinnedMesh.geometry.getAttribute("skinWeight");const boneTexture = skinnedMesh.skeleton.boneTexture;console.log(skinnedMesh ,skinnedMesh.morphTargetInfluences);for (let i = 0; i < position.count; i++) {const i3 = i * 3;const i4 = i * 4;const coord = new THREE.Vector4(position.array[i3],position.array[i3 + 1],position.array[i3 + 2],1);const boneMatX = this.getBoneMatrix(boneTexture!,skinIndex.array[i4]);const boneMatY = this.getBoneMatrix(boneTexture!,skinIndex.array[i4 + 1]);const boneMatZ = this.getBoneMatrix(boneTexture!,skinIndex.array[i4 + 2]);const boneMatW = this.getBoneMatrix(boneTexture!,skinIndex.array[i4 + 3]);const skinVertex = coord.applyMatrix4(skinnedMesh.bindMatrix);const skinned = new THREE.Vector4();skinned.addVectors(skinned,skinVertex.clone().applyMatrix4(boneMatX).multiplyScalar(skinWeight.array[i4]));skinned.addVectors(skinned,skinVertex.clone().applyMatrix4(boneMatY).multiplyScalar(skinWeight.array[i4 + 1]));skinned.addVectors(skinned,skinVertex.clone().applyMatrix4(boneMatZ).multiplyScalar(skinWeight.array[i4 + 2]));skinned.addVectors(skinned,skinVertex.clone().applyMatrix4(boneMatW).multiplyScalar(skinWeight.array[i4 + 3]));// transformed = ( bindMatrixInverse * skinned ).xyz;const transformed = skinned.applyMatrix4(skinnedMesh.bindMatrixInverse);cloneTargetPosition.setXYZ(i,transformed.x,transformed.y,transformed.z);}skinnedMesh.updateMatrix();skinnedMesh.updateMatrixWorld(true);cloneTarget.applyMatrix4(skinnedMesh.matrix);skinnedMesh.parent.add(cloneTarget);cloneTarget.position.x += 3;// skinnedMesh.visible = false;// group.add(cloneTarget);});});getBoneMatrix(boneTexture: THREE.DataTexture, index: number) {// 可以简化成这一行// return new THREE.Matrix4().fromArray(boneTexture!.source.data.data,index * 16);//下面是模拟shder的计算const size = boneTexture!.source.data.width;const j = index * 4;const x = j % size;// glsl float 转换 int  小数被丢弃 向下取整const y = Math.floor(j / size);const v1 = this.texelFetch(boneTexture, x, y, size);const v2 = this.texelFetch(boneTexture, x + 1, y, size);const v3 = this.texelFetch(boneTexture, x + 2, y, size);const v4 = this.texelFetch(boneTexture, x + 3, y, size);return new THREE.Matrix4().fromArray([...v1, ...v2, ...v3, ...v4]);}texelFetch(boneTexture: THREE.DataTexture,x: number,y: number,size: number) {const row = 4;const column = size * 4;return [boneTexture.source.data.data[x * row + y * column],boneTexture.source.data.data[x * row + y * column + 1],boneTexture.source.data.data[x * row + y * column + 2],boneTexture.source.data.data[x * row + y * column + 3],];}
法线部分

shader的实现

mat4 skinMatrix = mat4( 0.0 );skinMatrix += skinWeight.x * boneMatX;skinMatrix += skinWeight.y * boneMatY;skinMatrix += skinWeight.z * boneMatZ;skinMatrix += skinWeight.w * boneMatW;skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;#ifdef USE_TANGENTobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;#endif

CPU模拟

// 法线
// objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;
const skinMatrix = new THREE.Matrix4();
boneMatX.multiplyScalar(skinWeight.array[i4]);
this.MatrixAdd(skinMatrix, boneMatX);
boneMatY.multiplyScalar(skinWeight.array[i4 + 1]);
this.MatrixAdd(skinMatrix, boneMatY);
boneMatZ.multiplyScalar(skinWeight.array[i4 + 2]);
this.MatrixAdd(skinMatrix, boneMatZ);
boneMatW.multiplyScalar(skinWeight.array[i4 + 3]);
this.MatrixAdd(skinMatrix, boneMatW);
const m4 = new THREE.Matrix4().multiply(skinnedMesh.bindMatrixInverse).multiply(skinMatrix).multiply(skinnedMesh.bindMatrix);
const normal = new THREE.Vector3(cloneTargetNormal.array[i3],cloneTargetNormal.array[i3 + 1],cloneTargetNormal.array[i3 + 2]
);
const objectNormal = normal.applyMatrix4(m4);
cloneTargetNormal.array[i3] = objectNormal.x;
cloneTargetNormal.array[i3 + 1] = objectNormal.y;
cloneTargetNormal.array[i3 + 2] = objectNormal.z;MatrixAdd(m1: THREE.Matrix4, m2: THREE.Matrix4) {for (let index = 0; index < 16; index++) {m1.elements[index] += m2.elements[index];}return m1;
}

👌
在这里插入图片描述

相关文章:

  • STM32CubeMX,定时器之定时功能,入门学习,如何设置prescaler,以及timer计算PWM输入捕获方法(重要)
  • 机器学习系列——(十五)随机森林回归
  • 【数据分享】1929-2023年全球站点的逐日平均风速数据(Shp\Excel\免费获取)
  • 搭建macOS开发环境-1:准备工作
  • 2.0 Zookeeper 安装配置
  • 从 F-Droid 安装 termux
  • 回归预测模型:MATLAB多项式回归
  • 深入探究 HTTP 简化:httplib 库介绍
  • MyBatis中#和$符的区别,sql注入问题,动态sql语句
  • 【Java】学习笔记:关于java.sql;
  • STM32 与 ARM 的联系
  • 2024.1.31力扣每日一题——找出不同元素数目差数组
  • Rust函数入门与函数重载
  • 小程序 常用组件
  • SpringCloud-Eureka服务注册中心测试实践
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 【css3】浏览器内核及其兼容性
  • 4个实用的微服务测试策略
  • Java Agent 学习笔记
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Python - 闭包Closure
  • scala基础语法(二)
  • 读懂package.json -- 依赖管理
  • 关于for循环的简单归纳
  • 回流、重绘及其优化
  • 计算机常识 - 收藏集 - 掘金
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 前嗅ForeSpider教程:创建模板
  • 使用SAX解析XML
  • 算法---两个栈实现一个队列
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • hi-nginx-1.3.4编译安装
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (42)STM32——LCD显示屏实验笔记
  • (四)Controller接口控制器详解(三)
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • ***监测系统的构建(chkrootkit )
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .net 反编译_.net反编译的相关问题
  • .NET构架之我见
  • .NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)
  • .NET连接数据库方式
  • .NET学习教程二——.net基础定义+VS常用设置
  • .Net语言中的StringBuilder:入门到精通
  • ::什么意思
  • @RequestParam详解
  • @Valid和@NotNull字段校验使用
  • [] 与 [[]], -gt 与 > 的比较
  • [2669]2-2 Time类的定义
  • [51nod1610]路径计数
  • [AIGC] MySQL存储引擎详解