2013-01-24 22:08:53 +00:00
|
|
|
//
|
2013-09-26 19:04:57 +00:00
|
|
|
// Copyright 2013 Pixar
|
2013-01-24 22:08:53 +00:00
|
|
|
//
|
2013-09-26 19:04:57 +00:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "Apache License")
|
|
|
|
// with the following modification; you may not use this file except in
|
|
|
|
// compliance with the Apache License and the following modification to it:
|
|
|
|
// Section 6. Trademarks. is deleted and replaced with:
|
2013-01-24 22:08:53 +00:00
|
|
|
//
|
2013-09-26 19:04:57 +00:00
|
|
|
// 6. Trademarks. This License does not grant permission to use the trade
|
|
|
|
// names, trademarks, service marks, or product names of the Licensor
|
|
|
|
// and its affiliates, except as required to comply with Section 4(c) of
|
|
|
|
// the License and to reproduce the content of the NOTICE file.
|
2013-01-24 22:08:53 +00:00
|
|
|
//
|
2013-09-26 19:04:57 +00:00
|
|
|
// You may obtain a copy of the Apache License at
|
2013-01-24 22:08:53 +00:00
|
|
|
//
|
2013-09-26 19:04:57 +00:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2013-07-18 21:19:50 +00:00
|
|
|
//
|
2013-09-26 19:04:57 +00:00
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the Apache License with the above modification is
|
|
|
|
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
// KIND, either express or implied. See the Apache License for the specific
|
|
|
|
// language governing permissions and limitations under the Apache License.
|
2013-01-24 22:08:53 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
|
2012-12-11 01:15:13 +00:00
|
|
|
// Consolidated documentation for the source code collectively known
|
|
|
|
// as `mayaViewer`:
|
|
|
|
//
|
|
|
|
// OpenSubdivShader.h/cpp
|
|
|
|
// OpenSubdivShaderOverride.h/cpp
|
|
|
|
// osdMeshData.h/cpp
|
|
|
|
// hbrUtil.h/cpp
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### OpenSubdivShader.h
|
|
|
|
//
|
|
|
|
// Class definition of custom MPxHwShaderNode for drawing OpenSubdiv patches.
|
|
|
|
class OpenSubdivShader : public MPxHwShaderNode
|
|
|
|
{
|
|
|
|
// Supports Viewport 2.0 rendering only.
|
|
|
|
// geometry/glGeometry, bind/glBind and unbind/glUnbind
|
|
|
|
// are not implemented at this time.
|
|
|
|
|
|
|
|
public:
|
|
|
|
/* ... Standard MPxHwShader methods and definitions ... */
|
|
|
|
|
|
|
|
private:
|
|
|
|
// ##### OSD attributes
|
|
|
|
static MObject aLevel; // Subdivision level
|
|
|
|
static MObject aTessFactor; // GPU tesselation factor
|
|
|
|
static MObject aScheme; // Subdivision scheme
|
|
|
|
static MObject aAdaptive; // Feature-adaptive toggle
|
|
|
|
static MObject aWireframe; // Wireframe display toggle
|
|
|
|
static MObject aKernel; // Computation kernel
|
|
|
|
static MObject aInterpolateBoundary; // Boundary interpolation flag
|
|
|
|
|
|
|
|
// ##### Material attributes
|
|
|
|
static MObject aDiffuse;
|
|
|
|
static MObject aAmbient;
|
|
|
|
static MObject aSpecular;
|
|
|
|
static MObject aShininess;
|
|
|
|
|
|
|
|
// ##### Texture attributes
|
|
|
|
static MObject aDiffuseMapFile; // Texture filename
|
|
|
|
static MObject aUVSet; // UV set (defaults to current UV set)
|
|
|
|
static MObject aInterpolateUVBoundary; // Boundary interpolation flag for face-varying data (UVs)
|
|
|
|
|
|
|
|
// ##### Shader attribute
|
|
|
|
static MObject aShaderSource; // Optional shader file
|
|
|
|
|
|
|
|
private:
|
|
|
|
/* ... private methods and member variables ... */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### OpenSubdivShader.cpp
|
|
|
|
//
|
|
|
|
// Custom MPxHwShaderNode for drawing OpenSubdiv patches
|
|
|
|
|
|
|
|
/* GLEW needs to be included before OSD and Maya headers */
|
|
|
|
#include <GL/glew.h>
|
|
|
|
|
|
|
|
/* ... System includes ... */
|
|
|
|
/* ... Maya includes ... */
|
|
|
|
|
|
|
|
// `dyu - difference between DrawContext and DrawRegistry?`
|
|
|
|
//
|
|
|
|
// `I could take a stab but you can explain this in your sleep right?`
|
|
|
|
#include <osd/glDrawContext.h>
|
|
|
|
#include <osd/glDrawRegistry.h>
|
|
|
|
|
|
|
|
|
|
|
|
// ##### Attribute declarations
|
|
|
|
/* MObject OpenSubdivShader::... */
|
|
|
|
|
|
|
|
// ##### shaderSource attribute
|
|
|
|
//
|
|
|
|
// A default shader is compiled into the plug-in.
|
|
|
|
// The shaderSource attribute offers the ability to modify or
|
|
|
|
// replace the shader without having to recompile the plug-in.
|
|
|
|
MObject OpenSubdivShader::aShaderSource; // Optional shader file
|
|
|
|
|
|
|
|
static const char *defaultShaderSource =
|
|
|
|
#include "shader.inc"
|
|
|
|
;
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### EffectDrawRegistry
|
|
|
|
// An OpenSubdiv application builds its own draw registry in
|
|
|
|
// order to define parameters needed for its shader. In our
|
|
|
|
// case we use the attributes from the OpenSubdivShader to set up
|
|
|
|
// parameters and #define directives to pass through subdivision
|
|
|
|
// options and to control draw style. Client/application must
|
|
|
|
// specialize OsdGLDrawRegistry in order to provide an appearance
|
|
|
|
// for objects. Built-in shaders to not contain lighting code.
|
|
|
|
|
|
|
|
// Draw styles for EffectDrawRegistry
|
|
|
|
enum Effect
|
|
|
|
{
|
|
|
|
kQuadFill = 0,
|
|
|
|
kQuadLine = 1,
|
|
|
|
kTriFill = 2,
|
|
|
|
kTriLine = 3,
|
|
|
|
kPoint = 4,
|
|
|
|
};
|
|
|
|
typedef std::pair<OpenSubdiv::OsdPatchDescriptor, Effect> EffectDesc;
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### Override of OpenSubdiv::OsdGLDrawRegistry
|
|
|
|
//
|
|
|
|
// At the very least this class needs to define _CreateDrawSourceConfig
|
|
|
|
// and _CreateDrawConfig in order to define shader content.
|
|
|
|
//
|
|
|
|
class EffectDrawRegistry : public OpenSubdiv::OsdGLDrawRegistry<EffectDesc>
|
|
|
|
{
|
|
|
|
|
|
|
|
public:
|
|
|
|
EffectDrawRegistry() : _isAdaptive(true),
|
|
|
|
_diffuseId(0),
|
|
|
|
_shaderSource( defaultShaderSource )
|
|
|
|
{}
|
|
|
|
|
|
|
|
// ##### Accessors
|
|
|
|
//
|
|
|
|
// When setInternalValueInContext() gets triggered for certain
|
|
|
|
// attributes it will set dirty flags causing the shaders to
|
|
|
|
// be rebuilt.
|
|
|
|
//
|
|
|
|
/* isAdaptive */
|
|
|
|
void setIsAdaptive( bool ad ) { resetIfChanged(ad, _isAdaptive); }
|
|
|
|
bool getIsAdaptive() const { return _isAdaptive; }
|
|
|
|
|
|
|
|
/* diffuseId */
|
|
|
|
void setDiffuseId( GLuint dId ) { resetIfChanged(dId, _diffuseId); }
|
|
|
|
GLuint getDiffuseId() const { return _diffuseId; }
|
|
|
|
|
|
|
|
/* shaderSource */
|
|
|
|
void setShaderSource( std::string const & src ) { resetIfChanged(src, _shaderSource); }
|
|
|
|
std::string const & getShaderSource() const { return _shaderSource; }
|
|
|
|
|
|
|
|
protected:
|
|
|
|
// ##### Build shader configuration
|
|
|
|
virtual SourceConfigType * _CreateDrawSourceConfig(DescType const & desc);
|
|
|
|
|
|
|
|
// ##### Compile and link the shader
|
|
|
|
virtual ConfigType * _CreateDrawConfig(DescType const & desc, SourceConfigType const * sconfig);
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
// Clear the registry if a value has changed, triggering a rebuild
|
|
|
|
template< typename T>
|
|
|
|
void resetIfChanged( T newVal, T& curVal )
|
|
|
|
{
|
|
|
|
if ( newVal != curVal ) {
|
|
|
|
curVal = newVal;
|
|
|
|
Reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parameters mirroring attributes that affect shader generation
|
|
|
|
bool _isAdaptive;
|
|
|
|
GLuint _diffuseId;
|
|
|
|
std::string _shaderSource;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### _CreateDrawSourceConfig
|
|
|
|
//
|
|
|
|
// Called by base registry when a draw configuration is requested.
|
|
|
|
// Returns a shader source configuration which consists of a
|
|
|
|
// set of shader source code and compile-time configuration
|
|
|
|
// defines. These are cached by the effect registry for
|
|
|
|
// efficient re-use when rebuilding shaders.
|
|
|
|
//
|
|
|
|
EffectDrawRegistry::SourceConfigType *
|
|
|
|
EffectDrawRegistry::_CreateDrawSourceConfig(DescType const & desc)
|
|
|
|
{
|
|
|
|
Effect effect = desc.second;
|
|
|
|
|
|
|
|
// Create base draw configuration
|
|
|
|
SourceConfigType * sconfig =
|
|
|
|
BaseRegistry::_CreateDrawSourceConfig(desc.first);
|
|
|
|
|
|
|
|
if (desc.first.type != OpenSubdiv::kPerVertex) {
|
|
|
|
// Per-vertex descriptors are use for uniform refinement
|
|
|
|
if (effect == kQuadFill) effect = kTriFill;
|
|
|
|
if (effect == kQuadLine) effect = kTriLine;
|
|
|
|
sconfig->geometryShader.AddDefine("SMOOTH_NORMALS");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Configuration for adaptive refinement
|
|
|
|
sconfig->vertexShader.version = "#version 410\n";
|
|
|
|
sconfig->vertexShader.source = _shaderSource;
|
|
|
|
sconfig->vertexShader.AddDefine("VERTEX_SHADER");
|
|
|
|
}
|
|
|
|
assert(sconfig);
|
|
|
|
|
|
|
|
// Enable feature-adaptive face-varying UV generation
|
|
|
|
if (_isAdaptive) {
|
|
|
|
sconfig->geometryShader.AddDefine("FVAR_ADAPTIVE");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enable diffuse texture display if there is a valid texture map
|
|
|
|
if (_diffuseId != 0) {
|
|
|
|
sconfig->fragmentShader.AddDefine("USE_DIFFUSE_MAP");
|
|
|
|
}
|
|
|
|
|
|
|
|
// NUM_ELEMENTS should be set to the same value that is specified
|
|
|
|
// for the "numElements" argument when creating OsdVertexBuffers,
|
|
|
|
// e.g. OsdGLVertexBuffer
|
|
|
|
sconfig->vertexShader.AddDefine("NUM_ELEMENTS", "3");
|
|
|
|
|
|
|
|
// Initialize geometry shader
|
|
|
|
sconfig->geometryShader.version = "#version 410\n";
|
|
|
|
sconfig->geometryShader.source = _shaderSource;
|
|
|
|
sconfig->geometryShader.AddDefine("GEOMETRY_SHADER");
|
|
|
|
|
|
|
|
// Initialize fragment shader
|
|
|
|
sconfig->fragmentShader.version = "#version 410\n";
|
|
|
|
sconfig->fragmentShader.source = _shaderSource;
|
|
|
|
sconfig->fragmentShader.AddDefine("FRAGMENT_SHADER");
|
|
|
|
|
|
|
|
// Set up directives according to draw style
|
|
|
|
switch (effect) {
|
|
|
|
case kQuadFill:
|
|
|
|
sconfig->geometryShader.AddDefine("PRIM_QUAD");
|
|
|
|
sconfig->geometryShader.AddDefine("GEOMETRY_OUT_FILL");
|
|
|
|
sconfig->fragmentShader.AddDefine("PRIM_QUAD");
|
|
|
|
sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_FILL");
|
|
|
|
break;
|
|
|
|
case kQuadLine:
|
|
|
|
sconfig->geometryShader.AddDefine("PRIM_QUAD");
|
|
|
|
sconfig->geometryShader.AddDefine("GEOMETRY_OUT_LINE");
|
|
|
|
sconfig->fragmentShader.AddDefine("PRIM_QUAD");
|
|
|
|
sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_LINE");
|
|
|
|
break;
|
|
|
|
case kTriFill:
|
|
|
|
sconfig->geometryShader.AddDefine("PRIM_TRI");
|
|
|
|
sconfig->geometryShader.AddDefine("GEOMETRY_OUT_FILL");
|
|
|
|
sconfig->fragmentShader.AddDefine("PRIM_TRI");
|
|
|
|
sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_FILL");
|
|
|
|
break;
|
|
|
|
case kTriLine:
|
|
|
|
sconfig->geometryShader.AddDefine("PRIM_TRI");
|
|
|
|
sconfig->geometryShader.AddDefine("GEOMETRY_OUT_LINE");
|
|
|
|
sconfig->fragmentShader.AddDefine("PRIM_TRI");
|
|
|
|
sconfig->fragmentShader.AddDefine("GEOMETRY_OUT_LINE");
|
|
|
|
break;
|
|
|
|
case kPoint:
|
|
|
|
sconfig->geometryShader.AddDefine("PRIM_POINT");
|
|
|
|
sconfig->fragmentShader.AddDefine("PRIM_POINT");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sconfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Global GL buffer IDs and binding points
|
|
|
|
GLuint g_transformUB = 0,
|
|
|
|
g_transformBinding = 0,
|
|
|
|
g_tessellationUB = 0,
|
|
|
|
g_tessellationBinding = 0,
|
|
|
|
g_lightingUB = 0,
|
|
|
|
g_lightingBinding = 0;
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### _CreateDrawConfig
|
|
|
|
//
|
|
|
|
// Called by base registry when a draw config is requested.
|
|
|
|
// Returns a compiled and linked shader program corresponding to
|
|
|
|
// a previously created DrawSourceConfig. The effect registry also
|
|
|
|
// caches these for efficient re-use.
|
|
|
|
//
|
|
|
|
EffectDrawRegistry::ConfigType *
|
|
|
|
EffectDrawRegistry::_CreateDrawConfig(
|
|
|
|
DescType const & desc)
|
|
|
|
SourceConfigType const * sconfig)
|
|
|
|
{
|
|
|
|
ConfigType * config = BaseRegistry::_CreateDrawConfig(desc.first, sconfig);
|
|
|
|
assert(config);
|
|
|
|
|
|
|
|
// Assign binding points to uniform blocks
|
|
|
|
/* struct Transform */
|
|
|
|
g_transformBinding = 0;
|
|
|
|
glUniformBlockBinding(config->program,
|
|
|
|
glGetUniformBlockIndex(config->program, "Transform"),
|
|
|
|
g_transformBinding);
|
|
|
|
|
|
|
|
/* struct Tessellation */
|
|
|
|
g_tessellationBinding = 1;
|
|
|
|
glUniformBlockBinding(config->program,
|
|
|
|
glGetUniformBlockIndex(config->program, "Tessellation"),
|
|
|
|
g_tessellationBinding);
|
|
|
|
|
|
|
|
/* struct Lighting */
|
|
|
|
g_lightingBinding = 2;
|
|
|
|
glUniformBlockBinding(config->program,
|
|
|
|
glGetUniformBlockIndex(config->program, "Lighting"),
|
|
|
|
g_lightingBinding);
|
|
|
|
|
|
|
|
// Specify texture buffer ID uniforms in shader
|
|
|
|
GLint loc;
|
|
|
|
if ((loc = glGetUniformLocation(config->program, "g_VertexBuffer")) != -1) {
|
|
|
|
glProgramUniform1i(config->program, loc, 0); // GL_TEXTURE0
|
|
|
|
}
|
|
|
|
if ((loc = glGetUniformLocation(config->program, "g_ValenceBuffer")) != -1) {
|
|
|
|
glProgramUniform1i(config->program, loc, 1); // GL_TEXTURE1
|
|
|
|
}
|
|
|
|
if ((loc = glGetUniformLocation(config->program, "g_QuadOffsetBuffer")) != -1) {
|
|
|
|
glProgramUniform1i(config->program, loc, 2); // GL_TEXTURE2
|
|
|
|
}
|
|
|
|
if ((loc = glGetUniformLocation(config->program, "g_patchLevelBuffer")) != -1) {
|
|
|
|
glProgramUniform1i(config->program, loc, 3); // GL_TEXTURE3
|
|
|
|
}
|
|
|
|
if ((loc = glGetUniformLocation(config->program, "g_uvFVarBuffer")) != -1) {
|
|
|
|
glProgramUniform1i(config->program, loc, 4); // GL_TEXTURE4
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK_GL_ERROR("CreateDrawConfig leave\n");
|
|
|
|
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### Shader construction and initialization
|
|
|
|
|
|
|
|
OpenSubdivShader::OpenSubdivShader() {...}
|
|
|
|
OpenSubdivShader::~OpenSubdivShader() {...}
|
|
|
|
void * OpenSubdivShader::creator() {...}
|
|
|
|
MStatus OpenSubdivShader::initialize() {...}
|
|
|
|
void OpenSubdivShader::postConstructor() {...}
|
|
|
|
|
|
|
|
MStatus
|
|
|
|
OpenSubdivShader::compute(const MPlug &, MDataBlock &)
|
|
|
|
{
|
|
|
|
return MS::kSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### updateAttributes
|
|
|
|
//
|
|
|
|
// Called by openSubdivShaderOverride in updateDG.
|
|
|
|
// Pulls values for all non-internal attributes.
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OpenSubdivShader::updateAttributes()
|
|
|
|
{
|
|
|
|
MObject object = thisMObject();
|
|
|
|
MFnDependencyNode depFn(object);
|
|
|
|
|
|
|
|
// Retrieve non-internal attributes
|
|
|
|
_diffuse = getColor(object, aDiffuse);
|
|
|
|
_ambient = getColor(object, aAmbient);
|
|
|
|
_specular = getColor(object, aSpecular);
|
|
|
|
|
|
|
|
getAttribute(object, aWireframe, &_wireframe);
|
|
|
|
getAttribute(object, aShininess, &_shininess);
|
|
|
|
|
|
|
|
// Pull on any plugs that might be connected
|
|
|
|
getAttribute(object, aDiffuseMapFile, &_diffuseMapFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### Main draw method
|
|
|
|
//
|
|
|
|
// Called by OpenSubdivShaderOverride for each renderItem.
|
|
|
|
// Binds the vertex buffer and calls glDrawElements for
|
|
|
|
// each patch.
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OpenSubdivShader::draw(const MHWRender::MDrawContext &mDrawContext,
|
|
|
|
OsdMeshData *data)
|
|
|
|
{
|
|
|
|
glPushAttrib(GL_POLYGON_BIT|GL_ENABLE_BIT);
|
|
|
|
|
|
|
|
if (_wireframe) {
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
|
|
} else {
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
|
|
}
|
|
|
|
|
|
|
|
CHECK_GL_ERROR("draw begin\n");
|
|
|
|
|
|
|
|
// If shader source attribute has changed, update effectRegistry
|
|
|
|
updateRegistry();
|
|
|
|
|
|
|
|
// Bind position vertex buffer
|
|
|
|
GLuint bPosition = data->bindPositionVBO();
|
|
|
|
OpenSubdiv::OsdGLDrawContext *osdDrawContext = data->getDrawContext();
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, bPosition);
|
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, 0);
|
|
|
|
glEnableVertexAttribArray(0);
|
|
|
|
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, osdDrawContext->patchIndexBuffer);
|
|
|
|
|
|
|
|
// Get list of patches from OSD
|
|
|
|
OpenSubdiv::OsdPatchArrayVector const & patches =
|
|
|
|
osdDrawContext->patchArrays;
|
|
|
|
|
|
|
|
// Draw patches
|
|
|
|
for (size_t i = 0; i < patches.size(); ++i) {
|
|
|
|
OpenSubdiv::OsdPatchArray const & patch = patches[i];
|
|
|
|
|
|
|
|
GLint surfaceProgram = bindProgram(mDrawContext, osdDrawContext, patch);
|
|
|
|
|
|
|
|
if (patch.desc.type != OpenSubdiv::kPerVertex) {
|
|
|
|
glPatchParameteri(GL_PATCH_VERTICES, patch.patchSize);
|
|
|
|
|
|
|
|
glDrawElements(GL_PATCHES,
|
|
|
|
patch.numIndices, GL_UNSIGNED_INT,
|
|
|
|
reinterpret_cast<void *>(patch.firstIndex *
|
|
|
|
sizeof(unsigned int)));
|
|
|
|
} else {
|
|
|
|
glDrawElements(_scheme == OsdMeshData::kLoop ? GL_TRIANGLES : GL_LINES_ADJACENCY,
|
|
|
|
patch.numIndices, GL_UNSIGNED_INT,
|
|
|
|
reinterpret_cast<void *>(patch.firstIndex *
|
|
|
|
sizeof(unsigned int)));
|
|
|
|
}
|
|
|
|
CHECK_GL_ERROR("post draw\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear state
|
|
|
|
glUseProgram(0);
|
|
|
|
glDisableVertexAttribArray(0);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
glPopAttrib();
|
|
|
|
|
|
|
|
CHECK_GL_ERROR("draw end\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### bindTexture
|
|
|
|
//
|
|
|
|
// Utility routine which binds a single texture map
|
|
|
|
//
|
|
|
|
GLuint
|
|
|
|
OpenSubdivShader::bindTexture( const MString& filename, int textureUnit )
|
|
|
|
{
|
|
|
|
GLuint textureId = 0;
|
|
|
|
|
|
|
|
MHWRender::MTexture *mTexture = _theTextureManager->acquireTexture(filename);
|
|
|
|
if (mTexture) {
|
|
|
|
textureId = *(GLuint*)mTexture->resourceHandle();
|
|
|
|
glActiveTexture(GL_TEXTURE0+textureUnit);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr,"Can't read texture file: \"%s\"\n", filename.asChar());
|
|
|
|
}
|
|
|
|
return textureId;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### updateRegistry
|
|
|
|
//
|
|
|
|
// Evaluates dirty flags and updates the effect registry if any
|
|
|
|
// attributes have changed that require the shader to be rebuilt
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OpenSubdivShader::updateRegistry()
|
|
|
|
{
|
|
|
|
/* If adaptive flag has changed, update the effectRegistry accordingly */
|
|
|
|
if (_adaptiveDirty) {
|
|
|
|
_effectRegistry.setIsAdaptive(_adaptive);
|
|
|
|
_adaptiveDirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
MHWRender::MRenderer *theRenderer = MHWRender::MRenderer::theRenderer();
|
|
|
|
_theTextureManager = theRenderer->getTextureManager();
|
|
|
|
|
|
|
|
/* If diffuse texture has changed, update the effectRegistry accordingly */
|
|
|
|
if (_diffuseMapDirty) {
|
|
|
|
GLuint diffMapId = bindTexture( _diffuseMapFile, DIFF_TEXTURE_UNIT );
|
|
|
|
_effectRegistry.setDiffuseId(diffMapId);
|
|
|
|
_diffuseMapDirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If shader source has changed, update the effectRegistry accordingly */
|
|
|
|
if (_shaderSourceDirty) {
|
|
|
|
if ( _shaderSource.empty() ) {
|
|
|
|
if ( _effectRegistry.getShaderSource() != defaultShaderSource ) {
|
|
|
|
_effectRegistry.setShaderSource(defaultShaderSource);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( _effectRegistry.getShaderSource() != _shaderSource ) {
|
|
|
|
_effectRegistry.setShaderSource(_shaderSource);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_shaderSourceDirty = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### bindProgram
|
|
|
|
//
|
|
|
|
// Do all the work to build and install shader including
|
|
|
|
// set up buffer blocks for uniform variables, set up
|
|
|
|
// default lighting parameters, pass material uniforms
|
|
|
|
// and bind texture buffers used by texture maps and by
|
|
|
|
// OpenSubdiv's built-in shading code.
|
|
|
|
//
|
|
|
|
GLuint
|
|
|
|
OpenSubdivShader::bindProgram(const MHWRender::MDrawContext & mDrawContext,
|
|
|
|
OpenSubdiv::OsdGLDrawContext *osdDrawContext,
|
|
|
|
const OpenSubdiv::OsdPatchArray & patch)
|
|
|
|
{
|
|
|
|
|
|
|
|
CHECK_GL_ERROR("bindProgram begin\n");
|
|
|
|
|
|
|
|
// Primitives are triangles for Loop subdivision, quads otherwise
|
|
|
|
Effect effect = (_scheme == OsdMeshData::kLoop) ? kTriFill : kQuadFill;
|
|
|
|
EffectDesc effectDesc( patch.desc, effect );
|
|
|
|
|
|
|
|
// Build shader
|
|
|
|
EffectDrawRegistry::ConfigType *
|
|
|
|
config = _effectRegistry.GetDrawConfig(effectDesc);
|
|
|
|
|
|
|
|
// Install shader
|
|
|
|
GLuint program = config->program;
|
|
|
|
glUseProgram(program);
|
|
|
|
|
|
|
|
// Update and bind transform state
|
|
|
|
struct Transform {
|
|
|
|
float ModelViewMatrix[16];
|
|
|
|
float ProjectionMatrix[16];
|
|
|
|
float ModelViewProjectionMatrix[16];
|
|
|
|
} transformData;
|
|
|
|
setMatrix(mDrawContext.getMatrix(MHWRender::MDrawContext::kWorldViewMtx),
|
|
|
|
transformData.ModelViewMatrix);
|
|
|
|
setMatrix(mDrawContext.getMatrix(MHWRender::MDrawContext::kProjectionMtx),
|
|
|
|
transformData.ProjectionMatrix);
|
|
|
|
setMatrix(mDrawContext.getMatrix(MHWRender::MDrawContext::kWorldViewProjMtx),
|
|
|
|
transformData.ModelViewProjectionMatrix);
|
|
|
|
|
|
|
|
if (!g_transformUB) {
|
|
|
|
glGenBuffers(1, &g_transformUB);
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, g_transformUB);
|
|
|
|
glBufferData(GL_UNIFORM_BUFFER,
|
|
|
|
sizeof(transformData), NULL, GL_STATIC_DRAW);
|
|
|
|
};
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, g_transformUB);
|
|
|
|
glBufferSubData(GL_UNIFORM_BUFFER,
|
|
|
|
0, sizeof(transformData), &transformData);
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
|
|
|
|
glBindBufferBase(GL_UNIFORM_BUFFER, g_transformBinding, g_transformUB);
|
|
|
|
|
|
|
|
#define MAX_LEVEL 16
|
|
|
|
|
|
|
|
// Update and bind tessellation state
|
|
|
|
struct Tessellation {
|
|
|
|
/* XXX: std140 layout requires vec4 aligns for every entry */
|
|
|
|
float TessLevelInner[MAX_LEVEL][4];
|
|
|
|
float TessLevelOuter[MAX_LEVEL][4];
|
|
|
|
int GregoryQuadOffsetBase;
|
2013-07-18 19:57:26 +00:00
|
|
|
int PrimitiveIdBase;
|
2012-12-11 01:15:13 +00:00
|
|
|
} tessellationData;
|
|
|
|
|
|
|
|
int tessFactor = 2 << _tessFactor;
|
|
|
|
for (int i = 0; i < MAX_LEVEL; ++i) {
|
|
|
|
tessellationData.TessLevelInner[i][0] = float(std::max(tessFactor >> i, 1));
|
|
|
|
tessellationData.TessLevelOuter[i][0] = float(std::max(tessFactor >> i, 1));
|
|
|
|
}
|
|
|
|
|
2013-07-18 19:57:26 +00:00
|
|
|
tessellationData.GregoryQuadOffsetBase = patch.GetQuadOffsetIndex();
|
|
|
|
tessellationData.PrimitiveIdBase = patch.GetPatchIndex();
|
2012-12-11 01:15:13 +00:00
|
|
|
|
|
|
|
if (!g_tessellationUB) {
|
|
|
|
glGenBuffers(1, &g_tessellationUB);
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, g_tessellationUB);
|
|
|
|
glBufferData(GL_UNIFORM_BUFFER,
|
|
|
|
sizeof(tessellationData), NULL, GL_STATIC_DRAW);
|
|
|
|
};
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, g_tessellationUB);
|
|
|
|
glBufferSubData(GL_UNIFORM_BUFFER,
|
|
|
|
0, sizeof(tessellationData), &tessellationData);
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
|
|
|
|
glBindBufferBase(GL_UNIFORM_BUFFER,
|
|
|
|
g_tessellationBinding,
|
|
|
|
g_tessellationUB);
|
|
|
|
|
|
|
|
|
|
|
|
// Update and bind lighting state
|
|
|
|
int numLights = mDrawContext.numberOfActiveLights();
|
|
|
|
struct Lighting {
|
|
|
|
struct Light {
|
|
|
|
float position[4];
|
|
|
|
float diffuse[4];
|
|
|
|
float ambient[4];
|
|
|
|
float specular[4];
|
|
|
|
} lightSource[2];
|
|
|
|
} lightingData;
|
|
|
|
memset(&lightingData, 0, sizeof(lightingData));
|
|
|
|
|
|
|
|
for (int i = 0; i < numLights && i < 2; ++i) {
|
|
|
|
MFloatPointArray positions;
|
|
|
|
MFloatVector direction;
|
|
|
|
float intensity;
|
|
|
|
MColor color;
|
|
|
|
bool hasDirection, hasPosition;
|
|
|
|
mDrawContext.getLightInformation(i, positions, direction, intensity,
|
|
|
|
color, hasDirection, hasPosition);
|
|
|
|
|
|
|
|
MMatrix modelView = mDrawContext.getMatrix(MHWRender::MDrawContext::kWorldViewMtx);
|
|
|
|
direction = MVector(direction) * modelView;
|
|
|
|
|
|
|
|
Lighting::Light &light = lightingData.lightSource[i];
|
|
|
|
if (hasDirection) {
|
|
|
|
light.position[0] = -direction[0];
|
|
|
|
light.position[1] = -direction[1];
|
|
|
|
light.position[2] = -direction[2];
|
|
|
|
|
|
|
|
for (int j = 0; j < 4; ++j) {
|
|
|
|
light.diffuse[j] = color[j] * intensity;
|
|
|
|
light.ambient[j] = color[j] * intensity;
|
|
|
|
light.specular[j] = color[j] * intensity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!g_lightingUB) {
|
|
|
|
glGenBuffers(1, &g_lightingUB);
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, g_lightingUB);
|
|
|
|
glBufferData(GL_UNIFORM_BUFFER,
|
|
|
|
sizeof(lightingData), NULL, GL_STATIC_DRAW);
|
|
|
|
};
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, g_lightingUB);
|
|
|
|
glBufferSubData(GL_UNIFORM_BUFFER,
|
|
|
|
0, sizeof(lightingData), &lightingData);
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
|
|
|
|
glBindBufferBase(GL_UNIFORM_BUFFER, g_lightingBinding, g_lightingUB);
|
|
|
|
|
|
|
|
// Update other uniforms
|
|
|
|
float color[4] = { 0, 0, 0, 1 };
|
|
|
|
_diffuse.get(color);
|
|
|
|
glProgramUniform4fv(program,
|
|
|
|
glGetUniformLocation(program, "diffuseColor"),
|
|
|
|
1, color);
|
|
|
|
_ambient.get(color);
|
|
|
|
glProgramUniform4fv(program,
|
|
|
|
glGetUniformLocation(program, "ambientColor"),
|
|
|
|
1, color);
|
|
|
|
_specular.get(color);
|
|
|
|
glProgramUniform4fv(program,
|
|
|
|
glGetUniformLocation(program, "specularColor"),
|
|
|
|
1, color);
|
|
|
|
glProgramUniform1f(program,
|
|
|
|
glGetUniformLocation(program, "shininess"),
|
|
|
|
_shininess);
|
|
|
|
|
|
|
|
// Bind diffuse map
|
|
|
|
if (_effectRegistry.getDiffuseId()!=0) {
|
|
|
|
GLint difmap = glGetUniformLocation(program, "diffuseMap");
|
|
|
|
glProgramUniform1i(program, difmap, DIFF_TEXTURE_UNIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bind all texture buffers
|
|
|
|
//
|
|
|
|
// OpenSubdiv's geometric shading code depends on additional
|
|
|
|
// GL texture buffers. These are managed by the DrawContext
|
|
|
|
// and must be bound for use by the program in addition to
|
|
|
|
// any buffers used by the client/application shading code.
|
|
|
|
if (osdDrawContext->vertexTextureBuffer) {
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glBindTexture(GL_TEXTURE_BUFFER,
|
|
|
|
osdDrawContext->vertexTextureBuffer);
|
|
|
|
}
|
|
|
|
if (osdDrawContext->vertexValenceTextureBuffer) {
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
|
|
glBindTexture(GL_TEXTURE_BUFFER,
|
|
|
|
osdDrawContext->vertexValenceTextureBuffer);
|
|
|
|
}
|
|
|
|
if (osdDrawContext->quadOffsetTextureBuffer) {
|
|
|
|
glActiveTexture(GL_TEXTURE2);
|
|
|
|
glBindTexture(GL_TEXTURE_BUFFER,
|
|
|
|
osdDrawContext->quadOffsetTextureBuffer);
|
|
|
|
}
|
|
|
|
if (osdDrawContext->patchLevelTextureBuffer) {
|
|
|
|
glActiveTexture(GL_TEXTURE3);
|
|
|
|
glBindTexture(GL_TEXTURE_BUFFER,
|
|
|
|
osdDrawContext->patchLevelTextureBuffer);
|
|
|
|
}
|
|
|
|
if (osdDrawContext->fvarDataTextureBuffer) {
|
|
|
|
glActiveTexture( GL_TEXTURE4 );
|
|
|
|
glBindTexture( GL_TEXTURE_BUFFER,
|
|
|
|
osdDrawContext->fvarDataTextureBuffer );
|
|
|
|
}
|
|
|
|
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
|
|
|
CHECK_GL_ERROR("bindProgram leave\n");
|
|
|
|
|
|
|
|
return program;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### OpenSubdivShaderOverride.h
|
|
|
|
//
|
|
|
|
// Viewport 2.0 override for OpenSubdivShader
|
|
|
|
//
|
|
|
|
|
|
|
|
class OpenSubdivShaderOverride : public MHWRender::MPxShaderOverride
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
/* ... Standard MPxShaderOverride methods and definitions ... */
|
|
|
|
|
|
|
|
private:
|
|
|
|
// ##### Pointer to associated shader
|
|
|
|
OpenSubdivShader *_shader;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### OpenSubdivShaderOverride.cpp
|
|
|
|
//
|
|
|
|
// Viewport 2.0 override for OpenSubdivShader, implementing
|
|
|
|
// custom shading for OpenSubdiv patches.
|
|
|
|
//
|
|
|
|
|
|
|
|
/* Include GLEW before Maya and OSD includes */
|
|
|
|
#include <GL/glew.h>
|
|
|
|
|
|
|
|
/* ... System includes ... */
|
|
|
|
/* ... Maya includes ... */
|
|
|
|
|
|
|
|
// Set up compute controllers for each available compute kernel.
|
|
|
|
//
|
|
|
|
// `dyu - what exactly is a compute controller?`
|
|
|
|
//
|
|
|
|
#include <osd/cpuComputeController.h>
|
|
|
|
OpenSubdiv::OsdCpuComputeController *g_cpuComputeController = 0;
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENMP
|
|
|
|
#include <osd/ompComputeController.h>
|
|
|
|
OpenSubdiv::OsdOmpComputeController *g_ompComputeController = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
#include <osd/clComputeController.h>
|
|
|
|
cl_context g_clContext;
|
|
|
|
cl_command_queue g_clQueue;
|
|
|
|
#include "../common/clInit.h"
|
|
|
|
OpenSubdiv::OsdCLComputeController *g_clComputeController = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
#include <osd/cudaComputeController.h>
|
|
|
|
extern void cudaInit();
|
|
|
|
OpenSubdiv::OsdCudaComputeController *g_cudaComputeController = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
OpenSubdivShaderOverride::OpenSubdivShaderOverride(const MObject &obj) {...}
|
|
|
|
OpenSubdivShaderOverride::~OpenSubdivShaderOverride() {...}
|
|
|
|
MHWRender::MPxShaderOverride* OpenSubdivShaderOverride::creator(const MObject &obj) {...}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### attrChangedCB
|
|
|
|
//
|
|
|
|
// Informs us whenever an attribute on the shape node changes.
|
|
|
|
// Overkill since we really only want to know if the topology
|
|
|
|
// changes (e.g. an edge crease is added or changed) but Maya
|
|
|
|
// doesn't give us access to a callback that fine-grained.
|
|
|
|
// MMessage::PolyTopologyChangedCallback sounds promising
|
|
|
|
// but only calls back on a single change for any edit
|
|
|
|
// (i.e. not while dragging).
|
|
|
|
//
|
|
|
|
/*static*/
|
|
|
|
void
|
|
|
|
OpenSubdivShaderOverride::attrChangedCB(MNodeMessage::AttributeMessage msg, MPlug & plug,
|
|
|
|
MPlug & otherPlug, void* clientData)
|
|
|
|
{
|
|
|
|
/* We only care if the plug is outMesh and the action is "evaluate" */
|
|
|
|
if ( msg & MNodeMessage::kAttributeEval ) {
|
|
|
|
OsdMeshData *meshData = (OsdMeshData*)clientData;
|
2013-01-10 19:14:41 +00:00
|
|
|
MFnDependencyNode depNodeFn(meshData->getDagPath().node());
|
2012-12-11 01:15:13 +00:00
|
|
|
if ( plug == depNodeFn.attribute("outMesh")) {
|
|
|
|
meshData->setMeshTopoDirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### addTopologyChangedCallbacks
|
|
|
|
//
|
|
|
|
// Add a callback to inform us when topology might be changing
|
|
|
|
// so we can update the HBR mesh accordingly.
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OpenSubdivShaderOverride::addTopologyChangedCallbacks( const MDagPath& dagPath, OsdMeshData *data )
|
|
|
|
{
|
|
|
|
MStatus status = MS::kSuccess;
|
|
|
|
|
|
|
|
// Extract shape node and add callback to let us know when an attribute changes
|
|
|
|
MDagPath meshDagPath = dagPath;
|
|
|
|
meshDagPath.extendToShape();
|
|
|
|
MObject shapeNode = meshDagPath.node();
|
|
|
|
MCallbackId id = MNodeMessage::addAttributeChangedCallback(shapeNode,
|
|
|
|
attrChangedCB, data, &status );
|
|
|
|
|
|
|
|
// Keep track so we can delete callbacks in destructor
|
|
|
|
if ( status ) {
|
|
|
|
_callbackIds.append( id );
|
|
|
|
} else {
|
|
|
|
cerr << "MNodeMessage.addCallback failed" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### initialize
|
|
|
|
//
|
|
|
|
// Set up vertex buffer descriptors and geometry requirements
|
|
|
|
//
|
|
|
|
MString
|
|
|
|
OpenSubdivShaderOverride::initialize(const MInitContext &initContext,
|
|
|
|
MInitFeedback &initFeedback)
|
|
|
|
{
|
|
|
|
MString empty;
|
|
|
|
|
|
|
|
// Roundabout way of getting positions pulled into our OsdBufferGenerator
|
|
|
|
// where we can manage the VBO memory size.
|
|
|
|
//
|
|
|
|
// Needs to be re-visited, re-factored, optimized, etc.
|
|
|
|
//
|
|
|
|
// `dyu - can we add any more explanation about why this is done this way?`
|
|
|
|
{
|
|
|
|
MHWRender::MVertexBufferDescriptor positionDesc(
|
|
|
|
empty,
|
|
|
|
MHWRender::MGeometry::kPosition,
|
|
|
|
MHWRender::MGeometry::kFloat,
|
|
|
|
3);
|
|
|
|
addGeometryRequirement(positionDesc);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
MHWRender::MVertexBufferDescriptor positionDesc(
|
|
|
|
"osdPosition",
|
|
|
|
MHWRender::MGeometry::kTangent,
|
|
|
|
MHWRender::MGeometry::kFloat,
|
|
|
|
3);
|
|
|
|
positionDesc.setSemanticName("osdPosition");
|
|
|
|
addGeometryRequirement(positionDesc);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build data object for managing OSD mesh, pass in as custom data
|
|
|
|
if (initFeedback.customData == NULL) {
|
|
|
|
OsdMeshData *data = new OsdMeshData(initContext.dagPath);
|
|
|
|
initFeedback.customData = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a Maya callback so we can rebuild HBR mesh if topology changes
|
|
|
|
addTopologyChangedCallbacks( initContext.dagPath, (OsdMeshData*)initFeedback.customData );
|
|
|
|
|
|
|
|
return MString("OpenSubdivShaderOverride");
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### updateDG
|
|
|
|
//
|
|
|
|
// Save pointer to shader so we have access down the line.
|
|
|
|
// Call shader to update any attributes it needs to.
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OpenSubdivShaderOverride::updateDG(MObject object)
|
|
|
|
{
|
|
|
|
if (object == MObject::kNullObj)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Save pointer to shader for access from draw()
|
|
|
|
_shader = static_cast<OpenSubdivShader*>(
|
|
|
|
MPxHwShaderNode::getHwShaderNodePtr(object));
|
|
|
|
|
|
|
|
// Get updated attributes from shader
|
|
|
|
if (_shader) {
|
|
|
|
_shader->updateAttributes();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
OpenSubdivShaderOverride::updateDevice()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
OpenSubdivShaderOverride::endUpdate()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### Override draw method.
|
|
|
|
//
|
|
|
|
// Setup draw state and call osdMeshData methods to setup
|
|
|
|
// and refine geometry. Call to shader to do actual drawing.
|
|
|
|
//
|
|
|
|
bool
|
|
|
|
OpenSubdivShaderOverride::draw(
|
|
|
|
MHWRender::MDrawContext &context,
|
|
|
|
const MHWRender::MRenderItemList &renderItemList) const
|
|
|
|
{
|
|
|
|
{
|
|
|
|
MHWRender::MStateManager *stateMgr = context.getStateManager();
|
|
|
|
static const MDepthStencilState * depthState = NULL;
|
|
|
|
if (!depthState) {
|
|
|
|
MDepthStencilStateDesc desc;
|
|
|
|
depthState = stateMgr->acquireDepthStencilState(desc);
|
|
|
|
}
|
|
|
|
static const MBlendState *blendState = NULL;
|
|
|
|
if (!blendState) {
|
|
|
|
MBlendStateDesc desc;
|
|
|
|
|
|
|
|
int ntargets = desc.independentBlendEnable ?
|
|
|
|
MHWRender::MBlendState::kMaxTargets : 1;
|
|
|
|
|
|
|
|
for (int i = 0; i < ntargets; ++i) {
|
|
|
|
desc.targetBlends[i].blendEnable = false;
|
|
|
|
}
|
|
|
|
blendState = stateMgr->acquireBlendState(desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
stateMgr->setDepthStencilState(depthState);
|
|
|
|
stateMgr->setBlendState(blendState);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < renderItemList.length(); i++)
|
|
|
|
{
|
|
|
|
const MHWRender::MRenderItem *renderItem = renderItemList.itemAt(i);
|
|
|
|
OsdMeshData *data =
|
|
|
|
static_cast<OsdMeshData*>(renderItem->customData());
|
|
|
|
if (data == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If attributes or topology have changed which affect
|
|
|
|
// the HBR mesh it will be regenerated here.
|
|
|
|
data->rebuildHbrMeshIfNeeded(_shader);
|
|
|
|
|
|
|
|
const MHWRender::MVertexBuffer *position = NULL;
|
|
|
|
{
|
|
|
|
const MHWRender::MGeometry *geometry = renderItem->geometry();
|
|
|
|
for (int i = 0; i < geometry->vertexBufferCount(); i++) {
|
|
|
|
const MHWRender::MVertexBuffer *vb = geometry->vertexBuffer(i);
|
|
|
|
const MHWRender::MVertexBufferDescriptor &vdesc = vb->descriptor();
|
|
|
|
|
|
|
|
if (vdesc.name() == "osdPosition")
|
|
|
|
position = vb;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If HBR mesh was regenerated, rebuild FAR mesh factory
|
|
|
|
// and recreate OSD draw context
|
|
|
|
data->prepare();
|
|
|
|
|
|
|
|
// Refine geometry
|
|
|
|
data->updateGeometry(position);
|
|
|
|
|
|
|
|
// Draw patches
|
|
|
|
_shader->draw(context, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// #### OsdBufferGenerator
|
|
|
|
//
|
|
|
|
// Vertex buffer generator for OpenSubdiv geometry
|
|
|
|
//
|
|
|
|
|
|
|
|
// `dyu - might need some explanation from takahito here`
|
|
|
|
|
|
|
|
class OsdBufferGenerator : public MHWRender::MPxVertexBufferGenerator
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
OsdBufferGenerator() {}
|
|
|
|
virtual ~OsdBufferGenerator() {}
|
|
|
|
|
|
|
|
virtual bool getSourceIndexing(
|
|
|
|
const MDagPath &dagPath,
|
|
|
|
MHWRender::MComponentDataIndexing &sourceIndexing) const
|
|
|
|
{
|
|
|
|
MFnMesh mesh(dagPath.node());
|
|
|
|
|
|
|
|
MIntArray vertexCount, vertexList;
|
|
|
|
mesh.getVertices(vertexCount, vertexList);
|
|
|
|
|
|
|
|
MUintArray &vertices = sourceIndexing.indices();
|
|
|
|
for (unsigned int i = 0; i < vertexList.length(); ++i)
|
|
|
|
vertices.append((unsigned int)vertexList[i]);
|
|
|
|
|
|
|
|
sourceIndexing.setComponentType(MComponentDataIndexing::kFaceVertex);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool getSourceStreams(const MDagPath &dagPath,
|
|
|
|
MStringArray &) const
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if MAYA_API_VERSION >= 201350
|
|
|
|
virtual void createVertexStream(
|
|
|
|
const MDagPath &dagPath,
|
|
|
|
MVertexBuffer &vertexBuffer,
|
|
|
|
const MComponentDataIndexing &targetIndexing,
|
|
|
|
const MComponentDataIndexing &,
|
|
|
|
const MVertexBufferArray &) const
|
|
|
|
{
|
|
|
|
#else
|
|
|
|
virtual void createVertexStream(
|
|
|
|
const MDagPath &dagPath, MVertexBuffer &vertexBuffer,
|
|
|
|
const MComponentDataIndexing &targetIndexing) const
|
|
|
|
{
|
|
|
|
#endif
|
|
|
|
const MVertexBufferDescriptor &desc = vertexBuffer.descriptor();
|
|
|
|
|
|
|
|
MFnMesh meshFn(dagPath);
|
|
|
|
int nVertices = meshFn.numVertices();
|
|
|
|
MFloatPointArray points;
|
|
|
|
meshFn.getPoints(points);
|
|
|
|
|
|
|
|
#if MAYA_API_VERSION >= 201350
|
|
|
|
float *buffer = static_cast<float*>(vertexBuffer.acquire(nVertices, true));
|
|
|
|
#else
|
|
|
|
float *buffer = static_cast<float*>(vertexBuffer.acquire(nVertices));
|
|
|
|
#endif
|
|
|
|
float *dst = buffer;
|
|
|
|
for (int i = 0; i < nVertices; ++i) {
|
|
|
|
*dst++ = points[i].x;
|
|
|
|
*dst++ = points[i].y;
|
|
|
|
*dst++ = points[i].z;
|
|
|
|
}
|
|
|
|
vertexBuffer.commit(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static MPxVertexBufferGenerator *positionBufferCreator()
|
|
|
|
{
|
|
|
|
return new OsdBufferGenerator();
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### osdMeshData.h
|
|
|
|
//
|
|
|
|
// Class definition for OpenSubdiv MUserData used as custom data
|
|
|
|
// in OpenSubdivShaderOverride
|
|
|
|
//
|
|
|
|
|
|
|
|
// `dyu - hi again, need intelligent descriptions here too!`
|
|
|
|
// ` although maybe not if these are described elsewhere`
|
|
|
|
// ` getting a bit blurry now`
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <far/meshFactory.h>
|
|
|
|
#include <osd/glDrawContext.h>
|
|
|
|
|
|
|
|
#include <osd/cpuGLVertexBuffer.h>
|
|
|
|
#include <osd/cpuComputeContext.h>
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
#include <osd/clGLVertexBuffer.h>
|
|
|
|
#include <osd/clComputeContext.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
#include <osd/cudaGLVertexBuffer.h>
|
|
|
|
#include <osd/cudaComputeContext.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* ... Maya includes ... */
|
|
|
|
|
|
|
|
|
|
|
|
class OsdMeshData : public MUserData
|
|
|
|
{
|
|
|
|
...
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### osdMeshData.cpp
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
/* ... Maya includes ... */
|
|
|
|
|
|
|
|
// Set up compute controllers for each available compute kernel.
|
|
|
|
//
|
|
|
|
// `dyu - what exactly is a compute controller?`
|
|
|
|
// ` same decription from above...`
|
|
|
|
//
|
|
|
|
#include <osd/cpuDispatcher.h>
|
|
|
|
#include <osd/cpuComputeController.h>
|
|
|
|
extern OpenSubdiv::OsdCpuComputeController *g_cpuComputeController;
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENMP
|
|
|
|
#include <osd/ompDispatcher.h>
|
|
|
|
#include <osd/ompComputeController.h>
|
|
|
|
extern OpenSubdiv::OsdOmpComputeController *g_ompComputeController;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
#include <osd/clDispatcher.h>
|
|
|
|
#include <osd/clComputeController.h>
|
|
|
|
extern cl_context g_clContext;
|
|
|
|
extern cl_command_queue g_clQueue;
|
|
|
|
extern OpenSubdiv::OsdCLComputeController *g_clComputeController;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
#include <osd/cudaDispatcher.h>
|
|
|
|
#include <osd/cudaComputeController.h>
|
|
|
|
extern OpenSubdiv::OsdCudaComputeController *g_cudaComputeController;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <osd/glDrawContext.h>
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### Constructor
|
|
|
|
//
|
|
|
|
// Initialize all context and buffers to NULL
|
|
|
|
//
|
|
|
|
OsdMeshData::OsdMeshData(const MDagPath& meshDagPath) {...}
|
|
|
|
: MUserData(false),
|
|
|
|
{
|
|
|
|
_cpuComputeContext = NULL;
|
|
|
|
_cpuPositionBuffer = NULL;
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
_clComputeContext = NULL;
|
|
|
|
_clPositionBuffer = NULL;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
_cudaComputeContext = NULL;
|
|
|
|
_cudaPositionBuffer = NULL;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### Destructor
|
|
|
|
//
|
|
|
|
// Delete meshes, clear contexts and buffers
|
|
|
|
//
|
|
|
|
OsdMeshData::~OsdMeshData()
|
|
|
|
{
|
|
|
|
delete _hbrmesh;
|
|
|
|
delete _farmesh;
|
|
|
|
delete _drawContext;
|
|
|
|
delete _fvarDesc;
|
|
|
|
|
|
|
|
clearComputeContextAndVertexBuffer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
OsdMeshData::clearComputeContextAndVertexBuffer()
|
|
|
|
{
|
|
|
|
delete _cpuComputeContext;
|
|
|
|
_cpuComputeContext = NULL;
|
|
|
|
delete _cpuPositionBuffer;
|
|
|
|
_cpuPositionBuffer = NULL;
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
delete _cudaComputeContext;
|
|
|
|
_cudaComputeContext = NULL;
|
|
|
|
delete _cudaPositionBuffer;
|
|
|
|
_cudaPositionBuffer = NULL;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
delete _clComputeContext;
|
|
|
|
_clComputeContext = NULL;
|
|
|
|
delete _clPositionBuffer;
|
|
|
|
_clPositionBuffer = NULL;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### buildUVList
|
|
|
|
//
|
|
|
|
// Face-varying data expects a list of per-face per-vertex
|
|
|
|
// floats. This method reads the UVs from the mesh and
|
|
|
|
// concatenates them into such a list.
|
|
|
|
//
|
|
|
|
MStatus
|
|
|
|
OsdMeshData::buildUVList( MFnMesh& meshFn, std::vector<float>& uvList )
|
|
|
|
{
|
|
|
|
MStatus status = MS::kSuccess;
|
|
|
|
|
|
|
|
MItMeshPolygon polyIt( _meshDagPath );
|
|
|
|
|
|
|
|
MFloatArray uArray;
|
|
|
|
MFloatArray vArray;
|
|
|
|
|
|
|
|
// If user hasn't given us a UV set, use the current one
|
|
|
|
MString *uvSetPtr=NULL;
|
|
|
|
if ( _uvSet.numChars() > 0 ) {
|
|
|
|
if (uvSetNameIsValid(meshFn, _uvSet)) {
|
|
|
|
uvSetPtr = &_uvSet;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
MGlobal::displayWarning(MString("OpenSubdivShader: uvSet \""+_uvSet+"\" does not exist."));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
uvSetPtr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pull UVs from Maya mesh
|
|
|
|
status = meshFn.getUVs( uArray, vArray, uvSetPtr );
|
|
|
|
MCHECK_RETURN(status, "OpenSubdivShader: Error reading UVs");
|
|
|
|
|
|
|
|
if ( uArray.length() == 0 || vArray.length() == 0 )
|
|
|
|
{
|
|
|
|
MGlobal::displayWarning("OpenSubdivShader: Mesh has no UVs");
|
|
|
|
return MS::kFailure;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initalize list of UV values
|
|
|
|
uvList.clear();
|
|
|
|
uvList.resize( meshFn.numFaceVertices()*2 );
|
|
|
|
int uvListIdx = 0;
|
|
|
|
|
|
|
|
// For each face-vertex copy UVs into list, adjusting for renderman orientation
|
|
|
|
for ( polyIt.reset(); !polyIt.isDone(); polyIt.next() )
|
|
|
|
{
|
|
|
|
int faceIdx = polyIt.index();
|
|
|
|
unsigned int numPolyVerts = polyIt.polygonVertexCount();
|
|
|
|
|
|
|
|
for ( unsigned int faceVertIdx = 0;
|
|
|
|
faceVertIdx < numPolyVerts;
|
|
|
|
faceVertIdx++ )
|
|
|
|
{
|
|
|
|
int uvIdx;
|
|
|
|
polyIt.getUVIndex( faceVertIdx, uvIdx, uvSetPtr );
|
|
|
|
// convert maya UV orientation to renderman orientation
|
|
|
|
uvList[ uvListIdx++ ] = uArray[ uvIdx ];
|
|
|
|
uvList[ uvListIdx++ ] = 1.0f - vArray[ uvIdx ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### rebuildHbrMeshIfNeeded
|
|
|
|
//
|
|
|
|
// If the topology of the mesh changes, or any attributes that affect
|
|
|
|
// how the mesh is computed the original HBR needs to be rebuilt
|
|
|
|
// which will trigger a rebuild of the FAR mesh and subsequent buffers.
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OsdMeshData::rebuildHbrMeshIfNeeded(OpenSubdivShader *shader)
|
|
|
|
{
|
|
|
|
MStatus status = MS::kSuccess;
|
|
|
|
|
|
|
|
if (!_meshTopoDirty && !shader->getHbrMeshDirty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
MFnMesh meshFn(_meshDagPath);
|
|
|
|
|
|
|
|
// Cache attribute values
|
|
|
|
_level = shader->getLevel();
|
|
|
|
_kernel = shader->getKernel();
|
|
|
|
_adaptive = shader->isAdaptive();
|
|
|
|
_uvSet = shader->getUVSet();
|
|
|
|
|
|
|
|
int level = (_level < 1) ? 1 : _level;
|
|
|
|
|
|
|
|
// Get Maya vertex topology and crease data
|
|
|
|
MIntArray vertexCount;
|
|
|
|
MIntArray vertexList;
|
|
|
|
meshFn.getVertices(vertexCount, vertexList);
|
|
|
|
|
|
|
|
MUintArray edgeIds;
|
|
|
|
MDoubleArray edgeCreaseData;
|
|
|
|
meshFn.getCreaseEdges(edgeIds, edgeCreaseData);
|
|
|
|
|
|
|
|
MUintArray vtxIds;
|
|
|
|
MDoubleArray vtxCreaseData;
|
|
|
|
meshFn.getCreaseVertices(vtxIds, vtxCreaseData);
|
|
|
|
|
|
|
|
if (vertexCount.length() == 0) return;
|
|
|
|
|
|
|
|
// Copy Maya vectors into std::vectors
|
|
|
|
std::vector<int> numIndices(&vertexCount[0], &vertexCount[vertexCount.length()]);
|
|
|
|
std::vector<int> faceIndices(&vertexList[0], &vertexList[vertexList.length()]);
|
|
|
|
std::vector<int> vtxCreaseIndices(&vtxIds[0], &vtxIds[vtxIds.length()]);
|
|
|
|
std::vector<double> vtxCreases(&vtxCreaseData[0], &vtxCreaseData[vtxCreaseData.length()]);
|
|
|
|
std::vector<double> edgeCreases(&edgeCreaseData[0], &edgeCreaseData[edgeCreaseData.length()]);
|
|
|
|
|
|
|
|
// Edge crease index is stored as pairs of vertex ids
|
|
|
|
int nEdgeIds = edgeIds.length();
|
|
|
|
std::vector<int> edgeCreaseIndices;
|
|
|
|
edgeCreaseIndices.resize(nEdgeIds*2);
|
|
|
|
for (int i = 0; i < nEdgeIds; ++i) {
|
|
|
|
int2 vertices;
|
|
|
|
status = meshFn.getEdgeVertices(edgeIds[i], vertices);
|
|
|
|
if (status.error()) {
|
|
|
|
MERROR(status, "OpenSubdivShader: Can't get edge vertices");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
edgeCreaseIndices[i*2] = vertices[0];
|
|
|
|
edgeCreaseIndices[i*2+1] = vertices[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert attribute enums to HBR enums (this is why the enums need to match)
|
|
|
|
HbrMeshUtil::SchemeType hbrScheme = (HbrMeshUtil::SchemeType) shader->getScheme();
|
|
|
|
OsdHbrMesh::InterpolateBoundaryMethod hbrInterpBoundary =
|
|
|
|
(OsdHbrMesh::InterpolateBoundaryMethod) shader->getInterpolateBoundary();
|
|
|
|
OsdHbrMesh::InterpolateBoundaryMethod hbrInterpUVBoundary =
|
|
|
|
(OsdHbrMesh::InterpolateBoundaryMethod) shader->getInterpolateUVBoundary();
|
|
|
|
|
|
|
|
|
|
|
|
// Clear any existing face-varying descriptor
|
|
|
|
if (_fvarDesc) {
|
|
|
|
delete _fvarDesc;
|
|
|
|
_fvarDesc = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read UV data from maya and build per-face per-vert list of UVs for HBR face-varying data
|
|
|
|
std::vector< float > uvList;
|
|
|
|
status = buildUVList( meshFn, uvList );
|
|
|
|
if (! status.error()) {
|
|
|
|
// Create face-varying data descriptor. The memory required for indices
|
|
|
|
// and widths needs to stay alive as the HBR library only takes in the
|
|
|
|
// pointers and assumes the client will maintain the memory so keep _fvarDesc
|
|
|
|
// around as long as _hbrmesh is around.
|
|
|
|
int fvarIndices[] = { 0, 1 };
|
|
|
|
int fvarWidths[] = { 1, 1 };
|
|
|
|
_fvarDesc = new FVarDataDesc( 2, fvarIndices, fvarWidths, 2, hbrInterpUVBoundary );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_fvarDesc && hbrScheme != HbrMeshUtil::kCatmark) {
|
|
|
|
MGlobal::displayWarning("Face-varying not yet supported for Loop/Bilinear, using Catmull-Clark");
|
|
|
|
hbrScheme = HbrMeshUtil::kCatmark;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert Maya mesh to internal HBR representation
|
|
|
|
_hbrmesh = ConvertToHBR(meshFn.numVertices(), numIndices, faceIndices,
|
|
|
|
vtxCreaseIndices, vtxCreases,
|
|
|
|
std::vector<int>(), std::vector<float>(),
|
|
|
|
edgeCreaseIndices, edgeCreases,
|
|
|
|
hbrInterpBoundary,
|
|
|
|
hbrScheme,
|
|
|
|
false, // no ptex
|
|
|
|
_fvarDesc,
|
|
|
|
_fvarDesc?&uvList:NULL); // yes fvar (if have UVs)
|
|
|
|
|
|
|
|
/* note: GL function can't be used in prepareForDraw API. */
|
|
|
|
// Set dirty flag for FAR mesh factory to regenerate
|
|
|
|
_needsInitializeMesh = true;
|
|
|
|
|
|
|
|
// Mesh topology data is up to date
|
|
|
|
_meshTopoDirty = false;
|
|
|
|
shader->setHbrMeshDirty(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### initializeMesh
|
|
|
|
//
|
|
|
|
// `dyu - some juicy details about mesh factory and what a compute context is`
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OsdMeshData::initializeMesh()
|
|
|
|
{
|
|
|
|
if (!_hbrmesh)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Create FAR mesh
|
|
|
|
OpenSubdiv::FarMeshFactory<OpenSubdiv::OsdVertex>
|
|
|
|
meshFactory(_hbrmesh, _level, _adaptive);
|
|
|
|
|
|
|
|
_farmesh = meshFactory.Create(false, /* ptex coords */
|
|
|
|
true); /* fvar data */
|
|
|
|
|
|
|
|
delete _hbrmesh;
|
|
|
|
_hbrmesh = NULL;
|
|
|
|
|
|
|
|
int numTotalVertices = _farmesh->GetNumVertices();
|
|
|
|
|
|
|
|
// Create context and vertex buffer
|
|
|
|
clearComputeContextAndVertexBuffer();
|
|
|
|
|
|
|
|
if (_kernel == kCPU) {
|
|
|
|
_cpuComputeContext = OpenSubdiv::OsdCpuComputeContext::Create(_farmesh);
|
|
|
|
_cpuPositionBuffer = OpenSubdiv::OsdCpuGLVertexBuffer::Create(3, numTotalVertices);
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENMP
|
|
|
|
} else if (_kernel == kOPENMP) {
|
|
|
|
_cpuComputeContext = OpenSubdiv::OsdCpuComputeContext::Create(_farmesh);
|
|
|
|
_cpuPositionBuffer = OpenSubdiv::OsdCpuGLVertexBuffer::Create(3, numTotalVertices);
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
} else if (_kernel == kCUDA) {
|
|
|
|
_cudaComputeContext = OpenSubdiv::OsdCudaComputeContext::Create(_farmesh);
|
|
|
|
_cudaPositionBuffer = OpenSubdiv::OsdCudaGLVertexBuffer::Create(3, numTotalVertices);
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
} else if (_kernel == kCL) {
|
|
|
|
_clComputeContext = OpenSubdiv::OsdCLComputeContext::Create(_farmesh, g_clContext);
|
|
|
|
_clPositionBuffer = OpenSubdiv::OsdCLGLVertexBuffer::Create(3, numTotalVertices,
|
|
|
|
g_clContext);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
_needsInitializeMesh = false;
|
|
|
|
|
|
|
|
// Get geometry from maya mesh
|
|
|
|
MFnMesh meshFn(_meshDagPath);
|
|
|
|
meshFn.getPoints(_pointArray);
|
|
|
|
|
|
|
|
_needsUpdate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### createKernelDrawContext
|
|
|
|
//
|
|
|
|
// `dyu - more juicy details`
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OsdMeshData::createKernelDrawContext()
|
|
|
|
{
|
|
|
|
delete _drawContext;
|
|
|
|
|
|
|
|
if (_kernel == kCPU) {
|
|
|
|
_drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
|
|
|
|
_cpuPositionBuffer,
|
|
|
|
false, // ptex coords?
|
|
|
|
true); // fvar data?
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENMP
|
|
|
|
} else if (_kernel == kOPENMP) {
|
|
|
|
_drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
|
|
|
|
_cpuPositionBuffer,
|
|
|
|
false, true);
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
} else if (_kernel == kCUDA) {
|
|
|
|
_drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
|
|
|
|
_cudaPositionBuffer,
|
|
|
|
false, true);
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
} else if (_kernel == kCL) {
|
|
|
|
_drawContext = OpenSubdiv::OsdGLDrawContext::Create(_farmesh,
|
|
|
|
_clPositionBuffer,
|
|
|
|
false, true);
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### prepare
|
|
|
|
//
|
|
|
|
// If HBR mesh has been rebuilt the mesh factory and draw context
|
|
|
|
// also need to be rebuilt.
|
|
|
|
//
|
|
|
|
// `dyu - it seems like this could be merged with rebuildHBR`
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OsdMeshData::prepare()
|
|
|
|
{
|
|
|
|
if (_needsInitializeMesh) {
|
|
|
|
initializeMesh();
|
|
|
|
createKernelDrawContext();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### updateGeometry
|
|
|
|
//
|
|
|
|
// Refine the geometry.
|
|
|
|
//
|
|
|
|
// `dyu - probably need more detail... do they need to know about the bind/subdata copy?`
|
|
|
|
//
|
|
|
|
void
|
|
|
|
OsdMeshData::updateGeometry(const MHWRender::MVertexBuffer *points)
|
|
|
|
{
|
|
|
|
int nCoarsePoints = _pointArray.length();
|
|
|
|
|
|
|
|
GLuint mayaPositionVBO = *static_cast<GLuint*>(points->resourceHandle());
|
|
|
|
int size = nCoarsePoints * 3 * sizeof(float);
|
|
|
|
|
|
|
|
if (_kernel == kCPU || _kernel == kOPENMP) {
|
|
|
|
float *d_pos = _cpuPositionBuffer->BindCpuBuffer();
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mayaPositionVBO);
|
|
|
|
glGetBufferSubData(GL_ARRAY_BUFFER, 0, size, d_pos);
|
|
|
|
g_cpuComputeController->Refine(_cpuComputeContext, _cpuPositionBuffer);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
} else if (_kernel == kCUDA) {
|
|
|
|
glBindBuffer(GL_COPY_READ_BUFFER, mayaPositionVBO);
|
|
|
|
glBindBuffer(GL_COPY_WRITE_BUFFER, _cudaPositionBuffer->BindVBO());
|
|
|
|
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
|
|
|
|
0, 0, size);
|
|
|
|
g_cudaComputeController->Refine(_cudaComputeContext, _cudaPositionBuffer);
|
|
|
|
|
|
|
|
glBindBuffer(GL_COPY_READ_BUFFER, 0);
|
|
|
|
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
} else if (_kernel == kCL) {
|
|
|
|
glBindBuffer(GL_COPY_READ_BUFFER, mayaPositionVBO);
|
|
|
|
glBindBuffer(GL_COPY_WRITE_BUFFER, _clPositionBuffer->BindVBO());
|
|
|
|
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER,
|
|
|
|
0, 0, size);
|
|
|
|
g_clComputeController->Refine(_clComputeContext, _clPositionBuffer);
|
|
|
|
|
|
|
|
glBindBuffer(GL_COPY_READ_BUFFER, 0);
|
|
|
|
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
_needsUpdate = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### bindPositionVBO
|
|
|
|
//
|
|
|
|
// Wrapper to bind position the correct vertex buffer
|
|
|
|
// according to the current compute kernel
|
|
|
|
//
|
|
|
|
GLuint
|
|
|
|
OsdMeshData::bindPositionVBO()
|
|
|
|
{
|
|
|
|
if (_kernel == kCPU) {
|
|
|
|
return _cpuPositionBuffer->BindVBO();
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENMP
|
|
|
|
} else if (_kernel == kOPENMP) {
|
|
|
|
return _cpuPositionBuffer->BindVBO();
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_CUDA
|
|
|
|
} else if (_kernel == kCUDA) {
|
|
|
|
return _cudaPositionBuffer->BindVBO();
|
|
|
|
#endif
|
|
|
|
#ifdef OPENSUBDIV_HAS_OPENCL
|
|
|
|
} else if (_kernel == kCL) {
|
|
|
|
return _clPositionBuffer->BindVBO();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### hbrUtil.h
|
|
|
|
//
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### Face-varying data descriptor
|
|
|
|
//
|
|
|
|
// Wrapper for basic information needed to request
|
|
|
|
// face-varying data allocation from HBR
|
|
|
|
//
|
|
|
|
class FVarDataDesc
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
|
|
|
// Must be instantiated with descriptor information
|
|
|
|
FVarDataDesc( int count,
|
|
|
|
const int *indices, // start index for each face-varying variable
|
|
|
|
const int *widths, // width for each face-varying variable
|
|
|
|
int width,
|
|
|
|
OsdHbrMesh::InterpolateBoundaryMethod
|
|
|
|
boundary=OsdHbrMesh::k_InterpolateBoundaryNone
|
|
|
|
)
|
|
|
|
{
|
|
|
|
_fvarCount = count;
|
|
|
|
_totalfvarWidth = width;
|
|
|
|
_fvarIndices.assign( indices, indices+count );
|
|
|
|
_fvarWidths.assign( widths, widths+count );
|
|
|
|
_interpBoundary = boundary;
|
|
|
|
}
|
|
|
|
|
|
|
|
~FVarDataDesc() {}
|
|
|
|
|
|
|
|
|
|
|
|
// ##### Accessors
|
|
|
|
int getCount() const { return _fvarCount; }
|
|
|
|
const int *getIndices() const { return &_fvarIndices.front(); }
|
|
|
|
const int *getWidths() const { return &_fvarWidths.front(); }
|
|
|
|
int getTotalWidth() const { return _totalfvarWidth; }
|
|
|
|
OsdHbrMesh::InterpolateBoundaryMethod
|
|
|
|
getInterpBoundary() const { return _interpBoundary; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
// ##### Face-varying data parameters
|
|
|
|
int _fvarCount; // Number of facevarying datums
|
|
|
|
std::vector<int> _fvarIndices; // Start indices of the facevarying data
|
|
|
|
std::vector<int> _fvarWidths; // Individual widths of facevarying data
|
|
|
|
int _totalfvarWidth; // Total widths of the facevarying data
|
|
|
|
|
|
|
|
// ##### Boundary interpolation
|
|
|
|
OsdHbrMesh::InterpolateBoundaryMethod _interpBoundary;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" OsdHbrMesh * ConvertToHBR( ... );
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
// ### hbrUtil.cpp
|
|
|
|
|
|
|
|
//
|
|
|
|
// #### ConvertToHBR
|
|
|
|
//
|
|
|
|
// Take Maya mesh topology input and build an HBR mesh. Optional
|
|
|
|
// input parameters direct the method to create Ptex texture
|
|
|
|
// coordinates or to copy given face-varying data to the
|
|
|
|
// HBR faces as they are created.
|
|
|
|
//
|
|
|
|
OsdHbrMesh *
|
|
|
|
ConvertToHBR( int nVertices,
|
|
|
|
std::vector<int> const & faceVertCounts,
|
|
|
|
std::vector<int> const & faceIndices,
|
|
|
|
std::vector<int> const & vtxCreaseIndices,
|
|
|
|
std::vector<double> const & vtxCreases,
|
|
|
|
std::vector<int> const & edgeCrease1Indices, // face index, local edge index
|
|
|
|
std::vector<float> const & edgeCreases1,
|
|
|
|
std::vector<int> const & edgeCrease2Indices, // 2 vertex indices (Maya friendly)
|
|
|
|
std::vector<double> const & edgeCreases2,
|
|
|
|
OsdHbrMesh::InterpolateBoundaryMethod interpBoundary,
|
|
|
|
HbrMeshUtil::SchemeType scheme,
|
|
|
|
bool usingPtex, // defaults to false
|
|
|
|
FVarDataDesc const * fvarDesc, // defaults to NULL
|
|
|
|
std::vector<float> const * fvarData // defaults to NULL
|
|
|
|
)
|
|
|
|
{
|
|
|
|
static OpenSubdiv::HbrBilinearSubdivision<OpenSubdiv::OsdVertex> _bilinear;
|
|
|
|
static OpenSubdiv::HbrLoopSubdivision<OpenSubdiv::OsdVertex> _loop;
|
|
|
|
static OpenSubdiv::HbrCatmarkSubdivision<OpenSubdiv::OsdVertex> _catmark;
|
|
|
|
|
|
|
|
// Build HBR mesh with/without face varying data, according to input data.
|
|
|
|
// If a face-varying descriptor is passed in its memory needs to stay
|
|
|
|
// alive as long as this hbrMesh is alive (for indices and widths arrays).
|
|
|
|
OsdHbrMesh *hbrMesh;
|
|
|
|
if ( fvarDesc )
|
|
|
|
{
|
|
|
|
if (scheme == HbrMeshUtil::kCatmark)
|
|
|
|
hbrMesh = new OsdHbrMesh(&_catmark, fvarDesc->getCount(),
|
|
|
|
fvarDesc->getIndices(),
|
|
|
|
fvarDesc->getWidths(),
|
|
|
|
fvarDesc->getTotalWidth());
|
|
|
|
else if (scheme == HbrMeshUtil::kLoop)
|
|
|
|
hbrMesh = new OsdHbrMesh(&_loop, fvarDesc->getCount(),
|
|
|
|
fvarDesc->getIndices(),
|
|
|
|
fvarDesc->getWidths(),
|
|
|
|
fvarDesc->getTotalWidth());
|
|
|
|
else
|
|
|
|
hbrMesh = new OsdHbrMesh(&_bilinear, fvarDesc->getCount(),
|
|
|
|
fvarDesc->getIndices(),
|
|
|
|
fvarDesc->getWidths(),
|
|
|
|
fvarDesc->getTotalWidth());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (scheme == HbrMeshUtil::kCatmark)
|
|
|
|
hbrMesh = new OsdHbrMesh(&_catmark);
|
|
|
|
else if (scheme == HbrMeshUtil::kLoop)
|
|
|
|
hbrMesh = new OsdHbrMesh(&_loop);
|
|
|
|
else
|
|
|
|
hbrMesh = new OsdHbrMesh(&_bilinear);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create empty verts: actual vertices initialized in UpdatePoints();
|
|
|
|
OpenSubdiv::OsdVertex v;
|
|
|
|
for (int i = 0; i < nVertices; ++i) {
|
|
|
|
hbrMesh->NewVertex(i, v);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<int> vIndex;
|
|
|
|
int nFaces = (int)faceVertCounts.size();
|
|
|
|
int fvcOffset = 0; // face-vertex count offset
|
|
|
|
int ptxIdx = 0;
|
|
|
|
|
|
|
|
// Collect vertex indices for each face and create HBR face
|
|
|
|
for (int fi = 0; fi < nFaces; ++fi)
|
|
|
|
{
|
|
|
|
int nFaceVerts = faceVertCounts[fi];
|
|
|
|
vIndex.resize(nFaceVerts);
|
|
|
|
|
|
|
|
bool valid = true;
|
|
|
|
for (int fvi = 0; fvi < nFaceVerts; ++fvi)
|
|
|
|
{
|
|
|
|
vIndex[fvi] = faceIndices[fvi + fvcOffset];
|
|
|
|
int vNextIndex = faceIndices[(fvi+1) % nFaceVerts + fvcOffset];
|
|
|
|
|
|
|
|
// Check for non-manifold face
|
|
|
|
OsdHbrVertex * origin = hbrMesh->GetVertex(vIndex[fvi]);
|
|
|
|
OsdHbrVertex * destination = hbrMesh->GetVertex(vNextIndex);
|
|
|
|
if (!origin || !destination) {
|
|
|
|
OSD_ERROR("ERROR : An edge was specified that connected a nonexistent vertex");
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (origin == destination) {
|
|
|
|
OSD_ERROR("ERROR : An edge was specified that connected a vertex to itself");
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
OsdHbrHalfedge * opposite = destination->GetEdge(origin);
|
|
|
|
if (opposite && opposite->GetOpposite()) {
|
|
|
|
OSD_ERROR("ERROR : A non-manifold edge incident to more than 2 faces was found");
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (origin->GetEdge(destination)) {
|
|
|
|
OSD_ERROR("ERROR : An edge connecting two vertices was specified more than once. "
|
|
|
|
"It's likely that an incident face was flipped");
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( valid )
|
|
|
|
{
|
|
|
|
if (scheme == HbrMeshUtil::kLoop) {
|
|
|
|
// For Loop subdivision, triangulate from vertex indices
|
|
|
|
int triangle[3];
|
|
|
|
triangle[0] = vIndex[0];
|
|
|
|
for (int fvi = 2; fvi < nFaceVerts; ++fvi) {
|
|
|
|
triangle[1] = vIndex[fvi-1];
|
|
|
|
triangle[2] = vIndex[fvi];
|
|
|
|
hbrMesh->NewFace(3, triangle, 0);
|
|
|
|
}
|
|
|
|
/* ptex not fully implemented for loop, yet */
|
|
|
|
/* fvar not fully implemented for loop, yet */
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// For Catmull-Clark subdivision, create a quad face from vertices
|
|
|
|
/* bilinear subdivision not fully implemented */
|
|
|
|
OsdHbrFace *face = hbrMesh->NewFace(nFaceVerts, &(vIndex[0]), 0);
|
|
|
|
|
|
|
|
if (usingPtex) {
|
|
|
|
// Ptex textures will be used, set up ptex coordinates
|
|
|
|
face->SetPtexIndex(ptxIdx);
|
|
|
|
ptxIdx += (nFaceVerts == 4) ? 1 : nFaceVerts;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fvarData) {
|
|
|
|
// Face-varying data has been passed in, get pointer to data
|
|
|
|
int fvarWidth = hbrMesh->GetTotalFVarWidth();
|
|
|
|
const float *faceData = &(*fvarData)[ fvcOffset*fvarWidth ];
|
|
|
|
|
|
|
|
// For each face vertex copy fvar data into hbr mesh
|
|
|
|
for(int fvi=0; fvi<nFaceVerts; ++fvi)
|
|
|
|
{
|
|
|
|
OsdHbrVertex *v = hbrMesh->GetVertex( vIndex[fvi] );
|
|
|
|
OsdHbrFVarData& fvarData = v->GetFVarData(face);
|
|
|
|
if ( ! fvarData.IsInitialized() )
|
|
|
|
{
|
|
|
|
fvarData.SetAllData( fvarWidth, faceData );
|
|
|
|
}
|
|
|
|
else if (!fvarData.CompareAll(fvarWidth, faceData))
|
|
|
|
{
|
|
|
|
// If data exists for this face vertex, but is different
|
|
|
|
// (e.g. we're on a UV seam) create another fvar datum
|
|
|
|
OsdHbrFVarData& fvarData = v->NewFVarData(face);
|
|
|
|
fvarData.SetAllData( fvarWidth, faceData );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Advance pointer to next set of face-varying data
|
|
|
|
faceData += fvarWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
OSD_ERROR("Face %d will be ignored\n", fi);
|
|
|
|
}
|
|
|
|
|
|
|
|
fvcOffset += nFaceVerts;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign boundary interpolation methods
|
|
|
|
hbrMesh->SetInterpolateBoundaryMethod(interpBoundary);
|
|
|
|
if ( fvarDesc )
|
|
|
|
hbrMesh->SetFVarInterpolateBoundaryMethod(fvarDesc->getInterpBoundary());
|
|
|
|
|
|
|
|
// Set edge crease in two different indexing way
|
|
|
|
size_t nEdgeCreases = edgeCreases1.size();
|
|
|
|
for (size_t i = 0; i < nEdgeCreases; ++i) {
|
|
|
|
if (edgeCreases1[i] <= 0.0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
OsdHbrHalfedge * e = hbrMesh->
|
|
|
|
GetFace(edgeCrease1Indices[i*2])->
|
|
|
|
GetEdge(edgeCrease1Indices[i*2+1]);
|
|
|
|
|
|
|
|
if (!e) {
|
|
|
|
OSD_ERROR("Can't find edge (face %d edge %d)\n",
|
|
|
|
edgeCrease1Indices[i*2], edgeCrease1Indices[i*2+1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
e->SetSharpness(static_cast<float>(edgeCreases1[i]));
|
|
|
|
}
|
|
|
|
nEdgeCreases = edgeCreases2.size();
|
|
|
|
for (size_t i = 0; i < nEdgeCreases; ++i) {
|
|
|
|
if (edgeCreases2[i] <= 0.0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
OsdHbrVertex * v0 = hbrMesh->GetVertex(edgeCrease2Indices[i*2]);
|
|
|
|
OsdHbrVertex * v1 = hbrMesh->GetVertex(edgeCrease2Indices[i*2+1]);
|
|
|
|
OsdHbrHalfedge * e = NULL;
|
|
|
|
|
|
|
|
if (v0 && v1)
|
|
|
|
if (!(e = v0->GetEdge(v1)))
|
|
|
|
e = v1->GetEdge(v0);
|
|
|
|
if (!e) {
|
|
|
|
OSD_ERROR("ERROR can't find edge");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
e->SetSharpness(static_cast<float>(edgeCreases2[i]));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set corner
|
|
|
|
{
|
|
|
|
size_t nVertexCreases = vtxCreases.size();
|
|
|
|
for (size_t i = 0; i < nVertexCreases; ++i) {
|
|
|
|
if (vtxCreases[i] <= 0.0)
|
|
|
|
continue;
|
|
|
|
OsdHbrVertex * v = hbrMesh->GetVertex(vtxCreaseIndices[i]);
|
|
|
|
if (!v) {
|
|
|
|
OSD_ERROR("Can't find vertex %d\n", vtxCreaseIndices[i]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
v->SetSharpness(static_cast<float>(vtxCreases[i]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call finish to complete build of HBR mesh
|
|
|
|
hbrMesh->Finish();
|
|
|
|
|
|
|
|
return hbrMesh;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// `dyu: do we need to have one of these dudes for shader.glsl?`
|