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;
}

👌
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/457330.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

坚持刷题 | 二叉树的直径

文章目录 题目考察点代码实现实现总结方便用迭代的方式实现吗&#xff1f;迭代实现迭代实现总结 Hello&#xff0c;大家好&#xff0c;我是阿月。坚持话题&#xff0c;老年痴呆追不上我&#xff0c;今天还有时间&#xff0c;那就再来一题吧&#xff1a;二叉树的直径 题目 543.…

【数据分享】1929-2023年全球站点的逐日降水量数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;说到常用的降水数据&#xff0c;最详细的降水数据是具体到气象监测站点的降水数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全…

网络爬虫,使用存放在C的谷歌驱动报错

月 06, 2024 11:43:40 上午 org.openqa.selenium.os.OsProcess checkForError 严重: org.apache.commons.exec.ExecuteException: Execution failed (Exit value: -559038737. Caused by java.io.IOException: Cannot run program "C:\chromedriver121.exe" (in dir…

预处理详解(下)

8. 命名约定 ⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。 那我们平时的⼀个习惯是&#xff1a; 把宏名全部⼤写 函数名不要全部⼤写 9. #undef 这条指令⽤于移除⼀个宏定义。 # undef NAME // 如果现存的⼀个名字需要被重新定义&#xff0c;那么…

vue3-内置组件-TransitionGroup

<TransitionGroup> 是一个内置组件&#xff0c;用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。 与 <Transition> 的区别 <TransitionGroup> 支持和 <Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器&…

编程实例分享,宠物诊所电子处方怎么开,兽医电子处方模板电子版操作教程

编程实例分享&#xff0c;宠物诊所电子处方怎么开&#xff0c;兽医电子处方模板电子版操作教程 一、前言 以下操作教程以 佳易王兽医电子处方软件V16.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、在系统 设置里可以设置打印参数&#x…

elasticsearch 同义词管理热更新

背景 项目有需要做一个同义词搜索的功能&#xff0c;就去研究了下es的同义词搜索功能&#xff0c;踩了不少坑记录下 同义词本地文件读取方式 如果只是需要同义词搜索&#xff0c;不需要管理和更新&#xff0c;es本体就能支持&#xff0c;我踩的坑基本也不在这&#xff0c;就…

CleanMyMac2024如何识别并清理垃圾文件?

CleanMyMac识别并清理垃圾文件的过程主要依赖于其强大的扫描功能和智能算法。以下是具体的步骤&#xff1a; 扫描垃圾文件&#xff1a;首先&#xff0c;用户需要打开CleanMyMac软件&#xff0c;并点击“智能扫描”功能。然后&#xff0c;软件将开始自动扫描Mac系统上的各种垃圾…

幻兽帕鲁怎么样?好玩? Mac版的玩《幻兽帕鲁》也很简单,只需三个步骤

幻兽帕鲁怎么样 幻兽帕鲁是一款集合了多种游戏元素的游戏&#xff0c;它巧妙地融合了《方舟:生存进化》的野外生存挑战、《荒野之息》的开放世界探索、《魔兽世界》的多元角色互动以及宝可梦的精灵捕捉与培养等经典游戏元素。游戏的核心系统是「帕鲁」捕获&#xff0c;你可以让…

Vue3父子组件传参

一&#xff0c;父子组件传参&#xff1a; 应用场景&#xff1a;父子组件传参 Vue3碎片&#xff1a;defineEmits&#xff0c;defineProps&#xff0c;ref&#xff0c;reactive&#xff0c;onMounted 1.父组件传子组件 a.父组件传参子组件 import { ref} from vue import OnChi…

C++PythonC# 三语言OpenCV从零开发(8):图像平滑处理

文章目录 相关链接前言图像资源图像平滑处理图像学知识补充(重点)什么是卷积什么是图像滤波什么是方框滤波和均值滤波 代码PythonCCsharp 总结 相关链接 C&Python&Csharp in OpenCV 专栏 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程&#xff08;附带课…

简单实验 spring cloud gateWay 路由实验 实验

1.概要 1.1 说明 微服务统一网关实验&#xff0c;这里简单实验一下路由的功能 1.2 实验步骤&#xff0c;使用下面这个工程作为基础工程添加了一个gateWay做如下使用 简单实践 spring cloud nacos nacos-server-2.3.0-CSDN博客 2 代码 2.1 工程文件 <?xml version&quo…