Skip to content

Commit

Permalink
WebGPURenderer: Add skinning.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mugen87 committed Aug 25, 2021
1 parent 2a9a10d commit a00b186
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 32 deletions.
3 changes: 2 additions & 1 deletion examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@
"webgpu_lights_selective",
"webgpu_materials",
"webgpu_rtt",
"webgpu_sandbox"
"webgpu_sandbox",
"webgpu_skinning"
],
"webaudio": [
"webaudio_orientation",
Expand Down
79 changes: 79 additions & 0 deletions examples/jsm/renderers/nodes/accessors/SkinningPositionNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Node from '../core/Node.js';
import AttributeNode from '../core/AttributeNode.js';
import FloatNode from '../inputs/FloatNode.js';
import Matrix4Node from '../inputs/Matrix4Node.js';
import TextureNode from '../inputs/TextureNode.js';
import PositionNode from './PositionNode.js';

import { NodeUpdateType } from '../core/constants.js';
import { SkinningPosition } from '../functions/Common.js';

class SkinningPositionNode extends Node {

constructor( skinnedMesh ) {

super( 'vec3' );

this.skinnedMesh = skinnedMesh;

this.updateType = NodeUpdateType.Object;

//

this.bindMatrixNode = new Matrix4Node( skinnedMesh.bindMatrix );
this.bindMatrixInverseNode = new Matrix4Node( skinnedMesh.bindMatrixInverse );
this.boneTextureSizeNode = new FloatNode();
this.boneTextureNode = new TextureNode();
this.position = new PositionNode();

}

generate( builder, output ) {

const type = this.getType( builder );
const nodeData = builder.getDataFromNode( this, builder.shaderStage );

let getSkinningCallNode = nodeData.getSkinningCallNode;

if ( getSkinningCallNode === undefined ) {

const skinIndexNode = new AttributeNode( 'skinIndex', 'uvec4' );
const skinWeightNode = new AttributeNode( 'skinWeight', 'vec4' );

getSkinningCallNode = SkinningPosition.call( {
index: skinIndexNode,
weight: skinWeightNode,
position: this.position,
bindMatrix: this.bindMatrixNode,
bindMatrixInverse: this.bindMatrixInverseNode,
boneTexture: this.boneTextureNode,
boneSampler: this.boneTextureNode,
boneTextureSize: this.boneTextureSizeNode
} );

nodeData.getSkinningCallNode = getSkinningCallNode;

}

const skinningSnipped = getSkinningCallNode.build( builder, type );

return builder.format( skinningSnipped, type, output );

}

update() {

const skeleton = this.skinnedMesh.skeleton;

skeleton.update();

if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();

this.boneTextureSizeNode.value = skeleton.boneTextureSize;
this.boneTextureNode.value = skeleton.boneTexture;

}

}

export default SkinningPositionNode;
7 changes: 4 additions & 3 deletions examples/jsm/renderers/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { NodeUpdateType } from './constants.js';

class NodeBuilder {

constructor( material, renderer ) {
constructor( object, renderer ) {

this.material = material;
this.object = object;
this.material = object.material;
this.renderer = renderer;

this.nodes = [];
Expand All @@ -30,7 +31,7 @@ class NodeBuilder {

this.context = {
keywords: new NodeKeywords(),
material: material
material: object.material
};

this.nodesData = new WeakMap();
Expand Down
34 changes: 34 additions & 0 deletions examples/jsm/renderers/nodes/functions/Common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import FunctionNode from '../core/FunctionNode.js';

export const BoneMatrix = new FunctionNode( `
mat4 getBoneMatrix( const in float i, const in texture2D boneTexture, const in sampler boneSampler, const in float boneTextureSize ) {
float j = i * 4.0;
float x = mod( j, float( boneTextureSize ) );
float y = floor( j / float( boneTextureSize ) );
float dx = 1.0 / float( boneTextureSize );
float dy = 1.0 / float( boneTextureSize );
y = dy * ( y + 0.5 );
vec4 v1 = texture( sampler2D( boneTexture, boneSampler ), vec2( dx * ( x + 0.5 ), y ) );
vec4 v2 = texture( sampler2D( boneTexture, boneSampler ), vec2( dx * ( x + 1.5 ), y ) );
vec4 v3 = texture( sampler2D( boneTexture, boneSampler ), vec2( dx * ( x + 2.5 ), y ) );
vec4 v4 = texture( sampler2D( boneTexture, boneSampler ), vec2( dx * ( x + 3.5 ), y ) );
mat4 bone = mat4( v1, v2, v3, v4 );
return bone;
}`
);

export const SkinningPosition = new FunctionNode( `
vec3 getSkinningPosition( const in vec4 index, const in vec4 weight, const in vec3 position, const in mat4 bindMatrix, const in mat4 bindMatrixInverse, const in texture2D boneTexture, const in sampler boneSampler, const in float boneTextureSize ) {
mat4 boneMatX = getBoneMatrix( index.x, boneTexture, boneSampler, boneTextureSize );
mat4 boneMatY = getBoneMatrix( index.y, boneTexture, boneSampler, boneTextureSize );
mat4 boneMatZ = getBoneMatrix( index.z, boneTexture, boneSampler, boneTextureSize );
mat4 boneMatW = getBoneMatrix( index.w, boneTexture, boneSampler, boneTextureSize );
vec4 skinVertex = bindMatrix * vec4( position, 1.0 );
vec4 skinned = vec4( 0.0 );
skinned += boneMatX * skinVertex * weight.x;
skinned += boneMatY * skinVertex * weight.y;
skinned += boneMatZ * skinVertex * weight.z;
skinned += boneMatW * skinVertex * weight.w;
return ( bindMatrixInverse * skinned ).xyz;
}`
).setIncludes( [ BoneMatrix ] );
8 changes: 4 additions & 4 deletions examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import NodeSlot from '../../nodes/core/NodeSlot.js';

class WebGLNodeBuilder extends NodeBuilder {

constructor( material, renderer, properties ) {
constructor( object, renderer, properties ) {

super( material, renderer );
super( object, renderer );

this.properties = properties;

this._parseMaterial();
this._parseObject();

}

_parseMaterial() {
_parseObject() {

const material = this.material;

Expand Down
4 changes: 2 additions & 2 deletions examples/jsm/renderers/webgl/nodes/WebGLNodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ function addCodeAfterSnippet( source, snippet, code ) {

}

Material.prototype.onBuild = function ( parameters, renderer ) {
Material.prototype.onBuild = function ( object, parameters, renderer ) {

new WebGLNodeBuilder( this, renderer, parameters ).build();
new WebGLNodeBuilder( object, renderer, parameters ).build();

let fragmentShader = parameters.fragmentShader;

Expand Down
13 changes: 0 additions & 13 deletions examples/jsm/renderers/webgpu/WebGPURenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -687,19 +687,6 @@ class WebGPURenderer {

} else if ( object.isMesh || object.isLine || object.isPoints ) {

if ( object.isSkinnedMesh ) {

// update skeleton only once in a frame

if ( object.skeleton.frame !== info.render.frame ) {

object.skeleton.update();
object.skeleton.frame = info.render.frame;

}

}

if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {

if ( this.sortObjects === true ) {
Expand Down
24 changes: 22 additions & 2 deletions examples/jsm/renderers/webgpu/nodes/ShaderLib.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ const ShaderLib = {
NODE_CODE
NODE_CODE_MVP
gl_Position = NODE_MVP;
vec4 transform = NODE_MVP;
#ifdef NODE_SKINNING
NODE_CODE_SKINNING
transform = NODE_SKINNING;
#endif
gl_Position = transform;
}`,

Expand Down Expand Up @@ -75,7 +85,17 @@ const ShaderLib = {
NODE_CODE
NODE_CODE_MVP
gl_Position = NODE_MVP;
vec4 transform = NODE_MVP;
#ifdef NODE_SKINNING
NODE_CODE_SKINNING
transform = NODE_SKINNING;
#endif
gl_Position = transform;
}`,

Expand Down
18 changes: 13 additions & 5 deletions examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import NodeBuilder from '../../nodes/core/NodeBuilder.js';
import MaterialNode from '../../nodes/accessors/MaterialNode.js';
import NormalNode from '../../nodes/accessors/NormalNode.js';
import ModelViewProjectionNode from '../../nodes/accessors/ModelViewProjectionNode.js';
import SkinningPositionNode from '../../nodes/accessors/SkinningPositionNode.js';
import LightContextNode from '../../nodes/lights/LightContextNode.js';
import ShaderLib from './ShaderLib.js';

class WebGPUNodeBuilder extends NodeBuilder {

constructor( material, renderer, lightNode = null ) {
constructor( object, renderer, lightNode = null ) {

super( material, renderer );
super( object, renderer );

this.lightNode = lightNode;

Expand All @@ -30,12 +31,13 @@ class WebGPUNodeBuilder extends NodeBuilder {

this.nativeShader = null;

this._parseMaterial();
this._parseObject();

}

_parseMaterial() {
_parseObject() {

const object = this.object;
const material = this.material;

// get shader
Expand Down Expand Up @@ -68,6 +70,12 @@ class WebGPUNodeBuilder extends NodeBuilder {

}

if ( object.isSkinnedMesh === true ) {

mvpNode.position = new SkinningPositionNode( object );

}

if ( material.positionNode !== undefined ) {

mvpNode.position = material.positionNode;
Expand Down Expand Up @@ -414,7 +422,7 @@ class WebGPUNodeBuilder extends NodeBuilder {

this.shaderStage = shaderStage;

keywords.include( this, this.nativeShader.fragmentShader );
keywords.include( this, this.nativeShader[ shaderStage + 'Shader' ] );

}

Expand Down
2 changes: 1 addition & 1 deletion examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class WebGPUNodes {

if ( nodeBuilder === undefined ) {

nodeBuilder = new WebGPUNodeBuilder( object.material, this.renderer, lightNode ).build();
nodeBuilder = new WebGPUNodeBuilder( object, this.renderer, lightNode ).build();

this.builders.set( object, nodeBuilder );

Expand Down
Loading

0 comments on commit a00b186

Please sign in to comment.