diff options
author | pathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-18 18:17:34 +0000 |
---|---|---|
committer | pathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-18 18:17:34 +0000 |
commit | d832c34a518a1cf072fe4e0f7e00ac158eb8cadd (patch) | |
tree | a6ba1136ded4fb68cdadeffe86051e20f262cd0e /o3d/samples | |
parent | 2bb044402ba016977b58014a22b0631ab7ed605d (diff) | |
download | chromium_src-d832c34a518a1cf072fe4e0f7e00ac158eb8cadd.zip chromium_src-d832c34a518a1cf072fe4e0f7e00ac158eb8cadd.tar.gz chromium_src-d832c34a518a1cf072fe4e0f7e00ac158eb8cadd.tar.bz2 |
o3d-webgl: Shader support for skinning.
Requires setting skinning in properly in o3djs/effect.js
BUG=none
TEST=o3d-webgl-samples/skinning.html runs much faster.
Review URL: http://codereview.chromium.org/2873071
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56558 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d/samples')
-rw-r--r-- | o3d/samples/o3d-webgl-samples/skinning.html | 2 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/draw_list.js | 10 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/effect.js | 29 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/param.js | 23 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/primitive.js | 11 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/skin.js | 319 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/stream.js | 10 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/vertex_source.js | 15 | ||||
-rw-r--r-- | o3d/samples/o3djs/effect.js | 92 | ||||
-rw-r--r-- | o3d/samples/o3djs/primitives.js | 3 |
10 files changed, 469 insertions, 45 deletions
diff --git a/o3d/samples/o3d-webgl-samples/skinning.html b/o3d/samples/o3d-webgl-samples/skinning.html index c1b75bc..f8450be 100644 --- a/o3d/samples/o3d-webgl-samples/skinning.html +++ b/o3d/samples/o3d-webgl-samples/skinning.html @@ -121,7 +121,7 @@ function initStep2(clientElements) { // Create a cylinder. var vertexInfo = o3djs.primitives.createCylinderVertices( - 40, 200, 12, 20, + 40, 200, 120, 200, [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], diff --git a/o3d/samples/o3d-webgl/draw_list.js b/o3d/samples/o3d-webgl/draw_list.js index 2c79e12..68f635d 100644 --- a/o3d/samples/o3d-webgl/draw_list.js +++ b/o3d/samples/o3d-webgl/draw_list.js @@ -140,11 +140,15 @@ o3d.DrawList.prototype.render = function() { var paramObjects = [ transform, drawElement, - element, + element + ]; + if (element.streamBank) { + paramObjects.push(element.streamBank); + } + paramObjects.push( material, effect, - o3d.Param.SAS - ]; + o3d.Param.SAS); effect.searchForParams_(paramObjects); diff --git a/o3d/samples/o3d-webgl/effect.js b/o3d/samples/o3d-webgl/effect.js index 65da4a3..3d9f85e 100644 --- a/o3d/samples/o3d-webgl/effect.js +++ b/o3d/samples/o3d-webgl/effect.js @@ -296,7 +296,8 @@ o3d.Effect.prototype.getUniforms_ = this.program_, this.gl.ACTIVE_UNIFORMS); for (var i = 0; i < numUniforms; ++i) { var info = this.gl.getActiveUniform(this.program_, i); - if (info.name.indexOf('[') != -1) { + var name = info.name; + if (name.indexOf('[') != -1) { // This is an array param and we need to individually query each item in // the array to get its location. var baseName = info.name.substring(0, info.name.indexOf('[')); @@ -312,10 +313,10 @@ o3d.Effect.prototype.getUniforms_ = }; } else { // Not an array param. - this.uniforms_[info.name] = { + this.uniforms_[name] = { info: info, kind: o3d.Effect.ELEMENT, - location: this.gl.getUniformLocation(this.program_, info.name) + location: this.gl.getUniformLocation(this.program_, name) }; } } @@ -391,9 +392,22 @@ o3d.Effect.semanticMap_ = { 'texCoord4': {semantic: o3d.Stream.TEXCOORD, index: 4, gl_index: 9}, 'texCoord5': {semantic: o3d.Stream.TEXCOORD, index: 5, gl_index: 10}, 'texCoord6': {semantic: o3d.Stream.TEXCOORD, index: 6, gl_index: 11}, - 'texCoord7': {semantic: o3d.Stream.TEXCOORD, index: 7, gl_index: 12} + 'texCoord7': {semantic: o3d.Stream.TEXCOORD, index: 7, gl_index: 12}, + 'influenceWeights': {semantic: o3d.Stream.INFLUENCE_WEIGHTS, index: 0, + gl_index: 13}, + 'influenceIndices': {semantic: o3d.Stream.INFLUENCE_INDICES, index: 0, + gl_index: 14} }; +o3d.Effect.reverseSemanticMap_ = []; +(function(){ + var revmap = o3d.Effect.reverseSemanticMap_; + for (var key in o3d.Effect.semanticMap_) { + var value = o3d.Effect.semanticMap_[key]; + revmap[value.semantic] = revmap[value.semantic] || []; + revmap[value.semantic][value.index] = value.gl_index; + } +})(); /** * For each of the effect's uniform parameters, creates corresponding @@ -419,7 +433,12 @@ o3d.Effect.prototype.createUniformParameters = if (!sasTypes[name]) { switch (uniformData.kind) { case o3d.Effect.ARRAY: - param_object.createParam(name, 'ParamParamArray'); + var param = param_object.createParam(name, 'ParamParamArray'); + var array = new o3d.ParamArray; + array.gl = this.gl; + array.resize(uniformData.info.size, + paramTypes[uniformData.info.type]); + param.value = array; break; case o3d.Effect.STRUCT: o3d.notImplemented(); diff --git a/o3d/samples/o3d-webgl/param.js b/o3d/samples/o3d-webgl/param.js index d30af44..0f8a819 100644 --- a/o3d/samples/o3d-webgl/param.js +++ b/o3d/samples/o3d-webgl/param.js @@ -341,6 +341,29 @@ o3d.ParamParamArray = function() { }; o3d.inherit('ParamParamArray', 'Param'); +/** + * Acts like ParamParamArray, but asks its owner object to update the array + * contents every time its value is queried. + * + * @constructor + * @extends {o3d.ParamParamArray} + */ +o3d.ParamParamArrayOutput = function() { + o3d.ParamParamArray.call(this); +}; +o3d.inherit('ParamParamArrayOutput', 'ParamParamArray'); +o3d.ParamParamArrayOutput.prototype.__defineGetter__("value", + function() { + this.owner_.updateOutputs(this); + return this.value_; + } +); +o3d.ParamParamArrayOutput.prototype.__defineSetter__("value", + function(value) { + // Creating a new array is fine. + this.value_ = value; + } +); /** * @constructor diff --git a/o3d/samples/o3d-webgl/primitive.js b/o3d/samples/o3d-webgl/primitive.js index 358c3955..7c15ac2 100644 --- a/o3d/samples/o3d-webgl/primitive.js +++ b/o3d/samples/o3d-webgl/primitive.js @@ -127,19 +127,20 @@ o3d.Primitive.prototype.render = function() { for (var semantic_index = 0; semantic_index < streams.length; ++semantic_index) { - var gl_index = semantic + semantic_index - 1; - var stream_param = streams[semantic_index]; + var gl_index = o3d.Effect.reverseSemanticMap_[semantic][semantic_index]; + var stream = streams[semantic_index].stream; + var field = stream.field; + var buffer = field.buffer; + var stream_param = streams[semantic_index]; while (!stream_param.owner_.updateStreams && stream_param.inputConnection) { stream_param = stream_param.inputConnection; } if (stream_param.owner_.updateStreams) { + // By now, stream_param should point to the SkinEval's streams. stream_param.owner_.updateStreams(); // Triggers updating. } - var stream = streams[semantic_index].stream; - var field = stream.field; - var buffer = field.buffer; this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer.gl_buffer_); this.gl.enableVertexAttribArray(gl_index); diff --git a/o3d/samples/o3d-webgl/skin.js b/o3d/samples/o3d-webgl/skin.js index ea16430..cbec71f 100644 --- a/o3d/samples/o3d-webgl/skin.js +++ b/o3d/samples/o3d-webgl/skin.js @@ -77,10 +77,105 @@ o3d.Skin = function() { * @private */ this.highest_influences_ = 0; + + /** + * True if the list of weights and matrix index streams could be downloaded. + * + * @type {boolean} + * @private + */ + this.supports_vertex_shader_ = false; + + /** + * Whether the WEIGHTS and MATRIX_INDICES buffers are up-to-date. + * + * @type {boolean} + * @private + */ + this.buffers_valid_ = false; + + /** + * Vertex buffer containing indices and weights for this skin. + * + * @type {VertexBuffer} + */ + this.vertex_buffer_ = null; + + /** + * Buffer in which to store vertex weights. + * weights_field_ will be passed into the WEIGHTS stream. + * + * @type {o3d.Field} + * @private + */ + this.weights_field_ = null; + + /** + * Buffer in which to store list of matrix_indices per vertex. + * matrix_indices_field_ will be passed into the MATRIX_INDICES stream. + * + * @type {o3d.Field} + * @private + */ + this.matrix_indices_field_ = null; }; o3d.inherit('Skin', 'NamedObject'); /** + * Updates the weights and indices vertex buffers attached to this skin, only + * if they have been invalidated by some param change. + * Also checks if we can support skinning based on the number of bones we need + * in this skin. + * @private + * @return {boolean} true if our mesh is small enough to enable skin shader. + */ +o3d.Skin.prototype.updateVertexShader = function() { + if (!this.buffers_valid_) { + var numcomp = 4; + var numvert = this.influences.length; + if (!this.weights_field_ && !this.matrix_indices_field_) { + var vertex_buffer = new o3d.VertexBuffer; + vertex_buffer.gl = this.gl; + this.weights_field_ = + vertex_buffer.createField("FloatField", numcomp); + this.matrix_indices_field_ = + vertex_buffer.createField("FloatField", numcomp); + vertex_buffer.allocateElements(numvert); + } + var ii, jj; + var weights_field = this.weights_field_; + var indices_field = this.matrix_indices_field_; + var highest_influences = this.getHighestInfluences(); + + this.buffers_valid_ = true; + weights_field.buffer.lock(); + indices_field.buffer.lock(); + var max_num_bones = o3d.SkinEval.getMaxNumBones(this); + this.supports_vertex_shader_ = (highest_influences <= numcomp) && + (this.inverseBindPoseMatrices.length <= max_num_bones); + if (this.supports_vertex_shader_) { + // NOTE: If you make these Array's instead, you must initialize to 0. + var weights_arr = new Float32Array(numvert * numcomp); + var indices_arr = new Float32Array(numvert * numcomp); + // Float32rray is initialized to 0 by default. + for (ii = 0; ii < numvert; ++ii) { + var influence = this.influences[ii]; + for (jj = 0; jj < influence.length && jj < numcomp * 2; jj += 2) { + indices_arr[ii * numcomp + jj / 2] = influence[jj]; + weights_arr[ii * numcomp + jj / 2] = influence[jj + 1]; + } + } + weights_field.setAt(0, weights_arr); + indices_field.setAt(0, indices_arr); + } + // Otherwise, weights will be filled with 0's by default. + weights_field.buffer.unlock(); + indices_field.buffer.unlock(); + } + return this.supports_vertex_shader_; +}; + +/** * Sets the influences for an individual vertex. * * @param {number} vertex_index The index of the vertex to set influences for. @@ -98,6 +193,7 @@ o3d.Skin.prototype.setVertexInfluences = function( } this.influences[vertex_index] = influences; this.info_valid_ = false; + this.buffers_valid_ = false; }; /** @@ -228,6 +324,13 @@ o3d.SkinEval = function() { this.bones_ = []; /** + * Float32 array containing all matrices in 3x4 format. + * @type {Float32Array} + * @private + */ + this.bone_array_ = null; + + /** * Cache of StreamInfo objects for input values. Saved to avoid reallocating. * @type {!Array<o3d.SkinEval.StreamInfo>} * @private @@ -240,6 +343,16 @@ o3d.SkinEval = function() { * @private */ this.output_stream_infos_ = []; + + /** + * The base matrix to subtract from the matrices before skinning. + * + * @type {ParamArray} + * @private + */ + this.createParam("boneToWorld3x4", "ParamParamArrayOutput"); + + this.usingSkinShader = 0.0; }; o3d.inherit('SkinEval', 'StreamBank'); @@ -251,6 +364,28 @@ o3d.inherit('SkinEval', 'StreamBank'); o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "base", "ParamMatrix4"); /** + * Non-zero if we are using a shader for skinning. + * + * Holds state of whether the boneToWorld3x4 uniform is being used. If its + * value has been read, copy over the original vertex buffers, else we + * assume the shader does not support skinning. + * + * Do not write to this value. Set disableShader to true instead. + * + * @type {number} + * @see disableShader + */ +o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "usingSkinShader", "ParamFloat"); + +/** + * Set this value to true to force skin not to use a shader. + * + * @type {boolean} + * @default false + */ +o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "disableShader", "ParamBoolean"); + +/** * The Skin to use for skinning. * @type {Skin} */ @@ -263,6 +398,62 @@ o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "skin", "ParamSkin"); o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "matrices", "ParamArray"); /** + * Skins use 3 vec4's per matrix, since the last row is redundant. + * If we don't know, 32 is safe minimum value required by standard = (128-32)/3. + * @param {!o3d.NamedObject} obj Some object with access to gl. + * @return {number} Maximum number of bones allowed for shader-based skinning. + */ +o3d.SkinEval.getMaxNumBones = function(obj) { + // Quote from spec: + // GL_MAX_VERTEX_UNIFORM_VECTORS + // params returns one value, the maximum number of four-element + // floating-point, integer, or boolean vectors that can be held in + // uniform variable storage for a vertex shader. + // The value must be at least 128. See glUniform. + var gl = obj.gl; + var maxVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS); + return Math.floor((maxVertexUniformVectors - 32) / 3); +}; + +/** + * Someone bound a stream to this SkinEval. Enable the shader on the primitive, + * and bind any additional weights or indices streams if necessary. + * + * @param {o3d.VertexSource} dest VertexSource that bound to this VertexSource. + * @param {o3d.ParamVertexBufferStream} dest_param Other param which was bound. + * @override + */ +o3d.SkinEval.prototype.streamWasBound_ = function( + dest, semantic, semantic_index) { + this.skin.updateVertexShader(); + + if (this.skin.weights_field_ && this.skin.matrix_indices_field_) { + var weights_stream = dest.getVertexStream(o3d.Stream.INFLUENCE_WEIGHTS, 0); + var indices_stream = dest.getVertexStream(o3d.Stream.INFLUENCE_INDICES, 0); + if (!weights_stream || !indices_stream || + weights_stream.field != this.skin.weights_field_ || + indices_stream.field != this.skin.matrix_indices_field_) { + dest.setVertexStream(o3d.Stream.INFLUENCE_WEIGHTS, 0, + this.skin.weights_field_, 0); + dest.setVertexStream(o3d.Stream.INFLUENCE_INDICES, 0, + this.skin.matrix_indices_field_, 0); + } + + var destParam = dest.getParam("boneToWorld3x4"); + if (!destParam) { + destParam = dest.createParam("boneToWorld3x4", "ParamParamArray"); + } + destParam.bind(this.getParam("boneToWorld3x4")); + + destParam = dest.getParam("usingSkinShader"); + if (!destParam) { + destParam = dest.createParam("usingSkinShader", "ParamFloat"); + } + destParam.bind(this.getParam("usingSkinShader")); + } +}; + +/** * Multiplies input by weight, and adds with and returns into output. * * @param {!Array<!Array<number>>} input Input matrix4 to weight. @@ -299,14 +490,16 @@ o3d.SkinEval.prototype.multiplyAdd_ = function(input, weight, output) { }; /** + * Initializes all the input and output streams for this mesh, but does no + * copying yet. You should follow with uninitStreams_ when you are done. + * * @param {o3d.Skin} skin The skin. * @private */ -o3d.SkinEval.prototype.doSkinning_ = function(skin) { +o3d.SkinEval.prototype.initStreams_ = function(skin) { var ii, jj, ll, num_streams; - var influences_array = skin.influences; - var num_vertices = influences_array.length; + var num_vertices = this.skin.influences.length; // Update our inputs, lock all the inputs and outputs and check that we have // the same number of vertices as vertex influences. for (ii = 0, num_streams = 0; ii < this.vertex_streams_.length; ++ii) { @@ -318,14 +511,14 @@ o3d.SkinEval.prototype.doSkinning_ = function(skin) { // Make sure our upstream streams are ready var input = source_param.inputConnection; if (input && input.isAClassName("ParamVertexBufferStream")) { - input.updateOutputs(); // will automatically mark us as valid. + input.owner_.updateStreams(); // will automatically mark us as valid. } else { // Mark source_param as valid so we don't evaluate a second time. // TODO(pathorn): Caching previous computed values. } var source_stream = source_param.stream; - if (source_stream.getMaxVertices() != num_vertices) { + if (source_stream.getMaxVertices_() != num_vertices) { // TODO: Change semantic below to semantic_name. this.gl.client.error_callback("SkinEval.doSkinning_: " + "stream " + source_stream.semantic + " index " @@ -370,7 +563,7 @@ o3d.SkinEval.prototype.doSkinning_ = function(skin) { + destination_param.className + " not ParamVertexBufferStream"); } var destination_stream = destination_param.stream; - if (destination_stream.getMaxVertices() != num_vertices) { + if (destination_stream.getMaxVertices_() != num_vertices) { this.gl.client.error_callback("SkinEval.doSkinning_: " + "stream " + destination_stream.semantic + " index " + destination_stream.semanticIndex + " targeted by SkinEval '" @@ -399,7 +592,41 @@ o3d.SkinEval.prototype.doSkinning_ = function(skin) { } } - // skin. + this.input_stream_infos_.length = num_streams; + this.output_stream_infos_.length = num_streams; +}; + +/** + * Saves the result of the skinning operation in the graphics hardware. + * + * @private + */ +o3d.SkinEval.prototype.uninitStreams_ = function() { + // Unlock any buffers that were locked during skinning + for (ii = 0; ii < this.input_stream_infos_.length; ++ii) { + this.input_stream_infos_[ii].uninit(); + } + for (ii = 0; ii < this.output_stream_infos_.length; ++ii) { + var output_streams = this.output_stream_infos_[ii]; + for (var jj = 0; jj < output_streams.length; ++jj) { + output_streams[jj].uninit(); + } + } +}; + +/** + * Does skinning in software. Performs the actual matrix-point mulitplications + * and saves the result in all the output streams. + * + * @private + */ +o3d.SkinEval.prototype.doSkinning_ = function() { + this.initStreams_(); + + var ii, jj, ll; + var influences_array = this.skin.influences; + var num_vertices = influences_array.length; + for (ii = 0; ii < num_vertices; ++ii) { var influences = influences_array[ii]; if (influences.length) { @@ -422,7 +649,7 @@ o3d.SkinEval.prototype.doSkinning_ = function(skin) { } // for each source, compute and copy to destination. - for (jj = 0; jj < num_streams; ++jj) { + for (jj = 0; jj < this.input_stream_infos_.length; ++jj) { var input_stream_info = this.input_stream_infos_[jj]; input_stream_info.compute_function_(accumulated_matrix); var output_streams = this.output_stream_infos_[jj]; @@ -433,10 +660,14 @@ o3d.SkinEval.prototype.doSkinning_ = function(skin) { } } } + + this.uninitStreams_(); }; /** - * Updates the Bones. + * Updates the bones from this.matrices. + * + * @private */ o3d.SkinEval.prototype.updateBones_ = function() { // Get our matrices. @@ -498,19 +729,71 @@ o3d.SkinEval.prototype.updateBones_ = function() { * Updates the VertexBuffers bound to streams on this VertexSource. */ o3d.SkinEval.prototype.updateStreams = function() { + if (this.disableShader || !this.usingSkinShader || + !this.skin.updateVertexShader()) { + this.updateBones_(); + this.doSkinning_(this.skin); + this.usingSkinShader = 0.0; + } +}; + +/** + * Should be called on boneToWorld3x4's get value. + * Updates ParamArray for bones uniform sent to vertex shader. + * + * @param {ParamParamArrayOutput} param The array uniform parameter to update. + * @return {ParamArray} The array containing all of the float4 params. + */ +o3d.SkinEval.prototype.updateOutputs = function(param) { this.updateBones_(); - this.doSkinning_(this.skin); - var ii; - // Unlock any buffers that were locked during skinning - for (ii = 0; ii < this.input_stream_infos_.length; ++ii) { - this.input_stream_infos_[ii].uninit(); + if (!this.bone_array_) { + this.bone_array_ = new o3d.ParamArray; + this.bone_array_.gl = this.gl; + var max_num_bones = o3d.SkinEval.getMaxNumBones(this); + this.bone_array_.resize(max_num_bones * 3, "ParamFloat4"); + param.value = this.bone_array_; } - for (ii = 0; ii < this.output_stream_infos_.length; ++ii) { - var output_streams = this.output_stream_infos_[ii]; - for (var jj = 0; jj < output_streams.length; ++jj) { - output_streams[jj].uninit(); + var boneArray = this.bone_array_; + var ii, jj; + if (!this.disableShader && this.skin.updateVertexShader()) { + // Is this the first time the param has been read? + if (!this.usingSkinShader) { + // If so, we disable skinning in software. + this.usingSkinShader = 1.0; + // Copy the default positions of all vertex buffers. + this.initStreams_(); + var num_vertices = this.skin.influences.length; + for (ii = 0; ii < this.input_stream_infos_.length; ++ii) { + var input_stream_info = this.input_stream_infos_[ii]; + var output_streams = this.output_stream_infos_[ii]; + for (jj = 0; jj < output_streams.length; ++jj) { + var values = input_stream_info.field_.getAt(0, num_vertices); + output_streams[jj].field_.setAt(0, values); + } + } + this.uninitStreams_(); + } + var row; + for (ii = 0; ii < this.bones_.length; ++ii) { + var bone = this.bones_[ii]; + row = boneArray.getParam(ii*3); + row.value[0] = bone[0][0]; + row.value[1] = bone[1][0]; + row.value[2] = bone[2][0]; + row.value[3] = bone[3][0]; + row = boneArray.getParam(ii*3 + 1); + row.value[0] = bone[0][1]; + row.value[1] = bone[1][1]; + row.value[2] = bone[2][1]; + row.value[3] = bone[3][1]; + row = boneArray.getParam(ii*3 + 2); + row.value[0] = bone[0][2]; + row.value[1] = bone[1][2]; + row.value[2] = bone[2][2]; + row.value[3] = bone[3][2]; } } + return boneArray; }; /** diff --git a/o3d/samples/o3d-webgl/stream.js b/o3d/samples/o3d-webgl/stream.js index 2008f90..322b137 100644 --- a/o3d/samples/o3d-webgl/stream.js +++ b/o3d/samples/o3d-webgl/stream.js @@ -41,7 +41,7 @@ o3d.Stream = function(semantic, semantic_index, field, start_index) { this.field = field; this.startIndex = start_index; }; -o3d.inherit('Stream', 'NamedObject') +o3d.inherit('Stream', 'NamedObject'); /** * @type {number} @@ -56,7 +56,9 @@ o3d.Stream.Semantic = goog.typedef; * TANGENT, * BINORMAL, * COLOR, - * TEXCOORD + * TEXCOORD, + * INFLUENCE_WEIGHTS, + * INFLUENCE_INDICES * * Semantics used when binding buffers to the streambank. They determine how * the Stream links up to the shader inputs. @@ -68,6 +70,8 @@ o3d.Stream.TANGENT = 3; o3d.Stream.BINORMAL = 4; o3d.Stream.COLOR = 5; o3d.Stream.TEXCOORD = 6; +o3d.Stream.INFLUENCE_WEIGHTS = 7; +o3d.Stream.INFLUENCE_INDICES = 8; @@ -106,7 +110,7 @@ o3d.Stream.prototype.startIndex = 0; * and its buffer. * @private */ -o3d.Stream.prototype.getMaxVertices = function() { +o3d.Stream.prototype.getMaxVertices_ = function() { var buffer = this.field.buffer; if (!buffer) return 0; diff --git a/o3d/samples/o3d-webgl/vertex_source.js b/o3d/samples/o3d-webgl/vertex_source.js index 0e9741d..18cccd3 100644 --- a/o3d/samples/o3d-webgl/vertex_source.js +++ b/o3d/samples/o3d-webgl/vertex_source.js @@ -62,7 +62,9 @@ o3d.VertexSource.prototype.bindStream = function( dest_param.stream.field.className && source_param.stream.field.numComponents == dest_param.stream.field.numComponents) { - return dest_param.bind(source_param); + dest_param.bind(source_param); + source.streamWasBound_(this, semantic, semantic_index); + return true; } } @@ -100,3 +102,14 @@ o3d.VertexSource.prototype.getVertexStreamParam = function( o3d.notImplemented(); }; +/** + * Used by bindStream. Derived classes may override if needed. + * + * @param {o3d.VertexSource} dest VertexSource that bound to this VertexSource. + * @param {o3d.ParamVertexBufferStream} dest_param Other param which was bound. + * @protected + */ +o3d.VertexSource.prototype.streamWasBound_ = function( + dest, semantic, semantic_index) { +}; + diff --git a/o3d/samples/o3djs/effect.js b/o3d/samples/o3djs/effect.js index dd49595..37c74d2 100644 --- a/o3d/samples/o3djs/effect.js +++ b/o3d/samples/o3djs/effect.js @@ -393,10 +393,11 @@ o3djs.effect.setLanguage = function(language) { * @param {boolean} specular Whether to include stuff for diffuse * calculations. * @param {boolean} bumpSampler Whether there is a bump sampler. + * @param {boolean} skinning Whether this mesh has a skin. * @return {string} The code for the declarations. */ o3djs.effect.buildAttributeDecls = - function(material, diffuse, specular, bumpSampler) { + function(material, diffuse, specular, bumpSampler, skinning) { var str = o3djs.effect.BEGIN_IN_STRUCT + o3djs.effect.ATTRIBUTE + o3djs.effect.FLOAT4 + ' ' + 'position' + o3djs.effect.semanticSuffix('POSITION') + ';\n'; @@ -404,6 +405,12 @@ o3djs.effect.buildAttributeDecls = str += o3djs.effect.ATTRIBUTE + o3djs.effect.FLOAT3 + ' ' + 'normal' + o3djs.effect.semanticSuffix('NORMAL') + ';\n'; } + if (skinning) { + str += o3djs.effect.ATTRIBUTE + o3djs.effect.FLOAT4 + ' influenceWeights' + + o3djs.effect.semanticSuffix('BLENDWEIGHT') + ';\n'; + str += o3djs.effect.ATTRIBUTE + o3djs.effect.FLOAT4 + ' influenceIndices' + + o3djs.effect.semanticSuffix('BLENDINDICES') + ';\n'; + } str += o3djs.effect.buildTexCoords(material, false) + o3djs.effect.buildBumpInputCoords(bumpSampler) + o3djs.effect.END_STRUCT; @@ -824,6 +831,21 @@ o3djs.effect.buildStandardShaderString = function(material, var p = o3djs.effect; var bumpSampler = material.getParam('bumpSampler'); var bumpUVInterpolant; + var skinning; + + var maxSkinInfluences = 4; + + // Hardcode reasonable maximum for number of skinning uniforms. + // glsl: Table 6.19: minimum MAX_VERTEX_UNIFORM_VECTORS is 128. + // (DX9 requires a minimum of 256, so not a problem in o3d). + var maxSkinUniforms = 36 * 3; + if (o3djs.base.o3d && o3djs.base.o3d.SkinEval && + o3djs.base.o3d.SkinEval.getMaxNumBones) { + maxSkinUniforms = o3d.SkinEval.getMaxNumBones(material) * 3; + skinning = true; + } else { + skinning = false; + } /** * Extracts the texture type from a texture param. @@ -892,6 +914,17 @@ o3djs.effect.buildStandardShaderString = function(material, }; /** + * If skinning is enabled, builds the bone matrix uniform variables needed + * for skinning. Otherwise, returns the empty string. + * @return {string} The effect code for skinning uniforms. + */ + var buildSkinningUniforms = function() { + return skinning ? 'uniform ' + p.FLOAT4 + ' boneToWorld3x4' + + '[' + maxSkinUniforms + '];\n' + + 'uniform float usingSkinShader;\n' : ''; + }; + + /** * Builds uniform parameters for a given color input. If the material * has a sampler parameter, a sampler uniform is created, otherwise a * float4 color value is created. @@ -953,6 +986,7 @@ o3djs.effect.buildStandardShaderString = function(material, var buildConstantShaderString = function(material, descriptions) { descriptions.push('constant'); return buildCommonVertexUniforms() + + buildSkinningUniforms() + buildVertexDecls(material, false, false) + p.beginVertexShaderMain() + positionVertexShaderCode() + @@ -980,6 +1014,7 @@ o3djs.effect.buildStandardShaderString = function(material, descriptions.push('lambert'); return buildCommonVertexUniforms() + buildLightingUniforms() + + buildSkinningUniforms() + buildVertexDecls(material, true, false) + p.beginVertexShaderMain() + p.buildUVPassthroughs(material) + @@ -1027,6 +1062,7 @@ o3djs.effect.buildStandardShaderString = function(material, descriptions.push('phong'); return buildCommonVertexUniforms() + buildLightingUniforms() + + buildSkinningUniforms() + buildVertexDecls(material, true, true) + p.beginVertexShaderMain() + p.buildUVPassthroughs(material) + @@ -1085,6 +1121,7 @@ o3djs.effect.buildStandardShaderString = function(material, descriptions.push('phong'); return buildCommonVertexUniforms() + buildLightingUniforms() + + buildSkinningUniforms() + buildVertexDecls(material, true, true) + p.beginVertexShaderMain() + p.buildUVPassthroughs(material) + @@ -1135,9 +1172,27 @@ o3djs.effect.buildStandardShaderString = function(material, * @return {string} The code for the vertex shader. */ var positionVertexShaderCode = function() { - return ' ' + p.VERTEX_VARYING_PREFIX + 'position = ' + - p.mul(p.ATTRIBUTE_PREFIX + - 'position', 'worldViewProjection') + ';\n'; + var attribute_position = p.ATTRIBUTE_PREFIX + 'position'; + if (skinning) { + return ' ' + p.FLOAT4 + ' weightedpos = ' + attribute_position + ';\n' + + ' for (int i = 0; i < ' + maxSkinInfluences + '; i++) {\n' + + ' ' + p.FLOAT4 + ' temp = ' + p.FLOAT4 + '(' + + 'dot(boneToWorld3x4[int(influenceIndices[i] * 3.0)], ' + + attribute_position + '),\n' + + ' dot(boneToWorld3x4[int(influenceIndices[i] * 3.0 + 1.0)], ' + + attribute_position + '),\n' + + ' dot(boneToWorld3x4[int(influenceIndices[i] * 3.0 + 2.0)], ' + + attribute_position + '),\n' + + ' 1.0);\n' + + ' weightedpos += usingSkinShader * influenceWeights[i] * ' + + '(temp - ' + attribute_position + ');\n' + + ' }\n' + + ' ' + p.VERTEX_VARYING_PREFIX + 'position = ' + + p.mul('weightedpos', 'worldViewProjection') + ';\n'; + } else { + return ' ' + p.VERTEX_VARYING_PREFIX + 'position = ' + + p.mul(attribute_position, 'worldViewProjection') + ';\n'; + } }; /** @@ -1145,10 +1200,29 @@ o3djs.effect.buildStandardShaderString = function(material, * @return {string} The code for the vertex shader. */ var normalVertexShaderCode = function() { - return ' ' + p.VERTEX_VARYING_PREFIX + 'normal = ' + - p.mul(p.FLOAT4 + '(' + - p.ATTRIBUTE_PREFIX + - 'normal, 0)', 'worldInverseTranspose') + '.xyz;\n'; + var attribute_normal = p.ATTRIBUTE_PREFIX + 'normal'; + if (skinning) { + return ' ' + p.FLOAT3 + ' weightednorm = ' + attribute_normal + ';\n' + + ' for (int i = 0; i < ' + maxSkinInfluences + '; i++) {\n' + + ' ' + p.FLOAT3 + ' temp = ' + p.FLOAT3 + '(' + + 'dot(boneToWorld3x4[int(influenceIndices[i] * 3.0)].xyz, ' + + attribute_normal + '),\n' + + ' dot(boneToWorld3x4[int(influenceIndices[i] * 3.0 + 1.0)].xyz, ' + + attribute_normal + '),\n' + + ' dot(boneToWorld3x4[int(influenceIndices[i] * 3.0 + 2.0)].xyz, ' + + attribute_normal + '));\n' + + ' weightednorm += usingSkinShader * influenceWeights[i] * ' + + '(temp - ' + attribute_normal + ');\n' + + ' }\n' + + ' ' + p.VERTEX_VARYING_PREFIX + 'normal = ' + + p.mul(p.FLOAT4 + '(' + 'weightednorm' + ', 0)', + 'worldInverseTranspose') + '.xyz;\n'; + } else { + return ' ' + p.VERTEX_VARYING_PREFIX + 'normal = ' + + p.mul(p.FLOAT4 + '(' + + attribute_normal + ', 0)', 'worldInverseTranspose') + + '.xyz;\n'; + } }; /** @@ -1228,7 +1302,7 @@ o3djs.effect.buildStandardShaderString = function(material, */ var buildVertexDecls = function(material, diffuse, specular) { return p.buildAttributeDecls( - material, diffuse, specular, bumpSampler) + + material, diffuse, specular, bumpSampler, skinning) + p.buildVaryingDecls( material, diffuse, specular, bumpSampler); }; diff --git a/o3d/samples/o3djs/primitives.js b/o3d/samples/o3djs/primitives.js index c698d93..f736305 100644 --- a/o3d/samples/o3djs/primitives.js +++ b/o3d/samples/o3djs/primitives.js @@ -540,6 +540,9 @@ o3djs.primitives.VertexInfoBase.prototype.createShapeByType = function( requiredStream.addElement(1, 1, 1, 1); } break; + case o3djs.base.o3d.Stream.INFLUENCE_WEIGHTS: + case o3djs.base.o3d.Stream.INFLUENCE_INDICES: + break; default: throw 'Missing stream for semantic ' + semantic + ' with semantic index ' + semanticIndex; |