diff options
Diffstat (limited to 'intern/cycles/render/mesh_subdivision.cpp')
-rw-r--r-- | intern/cycles/render/mesh_subdivision.cpp | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/intern/cycles/render/mesh_subdivision.cpp b/intern/cycles/render/mesh_subdivision.cpp new file mode 100644 index 00000000000..efb40efbb79 --- /dev/null +++ b/intern/cycles/render/mesh_subdivision.cpp @@ -0,0 +1,567 @@ +/* + * Copyright 2011-2016 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mesh.h" +#include "attribute.h" + +#include "subd_split.h" +#include "subd_patch.h" +#include "subd_patch_table.h" + +#include "util_foreach.h" + +CCL_NAMESPACE_BEGIN + +#ifdef WITH_OPENSUBDIV + +CCL_NAMESPACE_END + +#include <opensubdiv/far/topologyRefinerFactory.h> +#include <opensubdiv/far/primvarRefiner.h> +#include <opensubdiv/far/patchTableFactory.h> +#include <opensubdiv/far/patchMap.h> + +/* specializations of TopologyRefinerFactory for ccl::Mesh */ + +namespace OpenSubdiv { +namespace OPENSUBDIV_VERSION { +namespace Far { + template<> + bool TopologyRefinerFactory<ccl::Mesh>::resizeComponentTopology(TopologyRefiner& refiner, ccl::Mesh const& mesh) + { + setNumBaseVertices(refiner, mesh.verts.size()); + setNumBaseFaces(refiner, mesh.subd_faces.size()); + + ccl::Mesh::SubdFace* face = &mesh.subd_faces[0]; + + for(int i = 0; i < mesh.subd_faces.size(); i++, face++) { + setNumBaseFaceVertices(refiner, i, face->num_corners); + } + + return true; + } + + template<> + bool TopologyRefinerFactory<ccl::Mesh>::assignComponentTopology(TopologyRefiner& refiner, ccl::Mesh const& mesh) + { + ccl::Mesh::SubdFace* face = &mesh.subd_faces[0]; + + for(int i = 0; i < mesh.subd_faces.size(); i++, face++) { + IndexArray face_verts = getBaseFaceVertices(refiner, i); + + int* corner = &mesh.subd_face_corners[face->start_corner]; + + for(int j = 0; j < face->num_corners; j++, corner++) { + face_verts[j] = *corner; + } + } + + return true; + } + + template<> + bool TopologyRefinerFactory<ccl::Mesh>::assignComponentTags(TopologyRefiner& refiner, ccl::Mesh const& mesh) + { + const ccl::Mesh::SubdEdgeCrease* crease = mesh.subd_creases.data(); + + for(int i = 0; i < mesh.subd_creases.size(); i++, crease++) { + Index edge = findBaseEdge(refiner, crease->v[0], crease->v[1]); + + if(edge != INDEX_INVALID) { + setBaseEdgeSharpness(refiner, edge, crease->crease * 10.0f); + } + } + + for(int i = 0; i < mesh.verts.size(); i++) { + ConstIndexArray vert_edges = getBaseVertexEdges(refiner, i); + + if(vert_edges.size() == 2) { + float sharpness = refiner.getLevel(0).getEdgeSharpness(vert_edges[0]); + sharpness = std::min(sharpness, refiner.getLevel(0).getEdgeSharpness(vert_edges[1])); + + setBaseVertexSharpness(refiner, i, sharpness); + } + } + + return true; + } + + template<> + bool TopologyRefinerFactory<ccl::Mesh>::assignFaceVaryingTopology(TopologyRefiner& /*refiner*/, ccl::Mesh const& /*mesh*/) + { + return true; + } + + template<> + void TopologyRefinerFactory<ccl::Mesh>::reportInvalidTopology(TopologyError /*err_code*/, + char const */*msg*/, ccl::Mesh const& /*mesh*/) + { + } +} /* namespace Far */ +} /* namespace OPENSUBDIV_VERSION */ +} /* namespace OpenSubdiv */ + +CCL_NAMESPACE_BEGIN + +using namespace OpenSubdiv; + +/* struct that implements OpenSubdiv's vertex interface */ + +template<typename T> +struct OsdValue { + T value; + + OsdValue() {} + + void Clear(void* = 0) { + memset(&value, 0, sizeof(T)); + } + + void AddWithWeight(OsdValue<T> const& src, float weight) { + value += src.value * weight; + } +}; + +template<> +void OsdValue<uchar4>::AddWithWeight(OsdValue<uchar4> const& src, float weight) +{ + for(int i = 0; i < 4; i++) { + value[i] += (uchar)(src.value[i] * weight); + } +} + +/* class for holding OpenSubdiv data used during tessellation */ + +class OsdData { + Mesh* mesh; + vector<OsdValue<float3> > verts; + Far::TopologyRefiner* refiner; + Far::PatchTable* patch_table; + Far::PatchMap* patch_map; + +public: + OsdData() : mesh(NULL), refiner(NULL), patch_table(NULL), patch_map(NULL) {} + + ~OsdData() + { + delete refiner; + delete patch_table; + delete patch_map; + } + + void build_from_mesh(Mesh* mesh_) + { + mesh = mesh_; + + /* type and options */ + Sdc::SchemeType type = Sdc::SCHEME_CATMARK; + + Sdc::Options options; + options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY); + + /* create refiner */ + refiner = Far::TopologyRefinerFactory<Mesh>::Create(*mesh, + Far::TopologyRefinerFactory<Mesh>::Options(type, options)); + + /* adaptive refinement */ + int max_isolation = 10; + refiner->RefineAdaptive(Far::TopologyRefiner::AdaptiveOptions(max_isolation)); + + /* create patch table */ + Far::PatchTableFactory::Options patch_options; + patch_options.endCapType = Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS; + + patch_table = Far::PatchTableFactory::Create(*refiner, patch_options); + + /* interpolate verts */ + int num_refiner_verts = refiner->GetNumVerticesTotal(); + int num_local_points = patch_table->GetNumLocalPoints(); + + verts.resize(num_refiner_verts + num_local_points); + for(int i = 0; i < mesh->verts.size(); i++) { + verts[i].value = mesh->verts[i]; + } + + OsdValue<float3>* src = &verts[0]; + for(int i = 0; i < refiner->GetMaxLevel(); i++) { + OsdValue<float3>* dest = src + refiner->GetLevel(i).GetNumVertices(); + Far::PrimvarRefiner(*refiner).Interpolate(i+1, src, dest); + src = dest; + } + + patch_table->ComputeLocalPointValues(&verts[0], &verts[num_refiner_verts]); + + /* create patch map */ + patch_map = new Far::PatchMap(*patch_table); + } + + void subdivide_attribute(Attribute& attr) + { + Far::PrimvarRefiner primvar_refiner(*refiner); + + if(attr.element == ATTR_ELEMENT_VERTEX) { + int num_refiner_verts = refiner->GetNumVerticesTotal(); + int num_local_points = patch_table->GetNumLocalPoints(); + + attr.resize(num_refiner_verts + num_local_points); + attr.flags |= ATTR_FINAL_SIZE; + + char* src = &attr.buffer[0]; + + for(int i = 0; i < refiner->GetMaxLevel(); i++) { + char* dest = src + refiner->GetLevel(i).GetNumVertices() * attr.data_sizeof(); + + if(attr.same_storage(attr.type, TypeDesc::TypeFloat)) { + primvar_refiner.Interpolate(i+1, (OsdValue<float>*)src, (OsdValue<float>*&)dest); + } + else { + primvar_refiner.Interpolate(i+1, (OsdValue<float4>*)src, (OsdValue<float4>*&)dest); + } + + src = dest; + } + + if(attr.same_storage(attr.type, TypeDesc::TypeFloat)) { + patch_table->ComputeLocalPointValues((OsdValue<float>*)&attr.buffer[0], + (OsdValue<float>*)&attr.buffer[num_refiner_verts * attr.data_sizeof()]); + } + else { + patch_table->ComputeLocalPointValues((OsdValue<float4>*)&attr.buffer[0], + (OsdValue<float4>*)&attr.buffer[num_refiner_verts * attr.data_sizeof()]); + } + } + else if(attr.element == ATTR_ELEMENT_CORNER || attr.element == ATTR_ELEMENT_CORNER_BYTE) { + // TODO(mai): fvar interpolation + } + } + + friend struct OsdPatch; + friend class Mesh; +}; + +/* ccl::Patch implementation that uses OpenSubdiv for eval */ + +struct OsdPatch : Patch { + OsdData* osd_data; + + OsdPatch(OsdData* data) : osd_data(data) {} + + void eval(float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, float u, float v) + { + const Far::PatchTable::PatchHandle* handle = osd_data->patch_map->FindPatch(patch_index, u, v); + assert(handle); + + float p_weights[20], du_weights[20], dv_weights[20]; + osd_data->patch_table->EvaluateBasis(*handle, u, v, p_weights, du_weights, dv_weights); + + Far::ConstIndexArray cv = osd_data->patch_table->GetPatchVertices(*handle); + + float3 du, dv; + if(P) *P = make_float3(0.0f, 0.0f, 0.0f); + du = make_float3(0.0f, 0.0f, 0.0f); + dv = make_float3(0.0f, 0.0f, 0.0f); + + for(int i = 0; i < cv.size(); i++) { + float3 p = osd_data->verts[cv[i]].value; + + if(P) *P += p * p_weights[i]; + du += p * du_weights[i]; + dv += p * dv_weights[i]; + } + + if(dPdu) *dPdu = du; + if(dPdv) *dPdv = dv; + if(N) *N = normalize(cross(du, dv)); + } + + BoundBox bound() { return BoundBox::empty; } +}; + +#endif + +void Mesh::tessellate(DiagSplit *split) +{ +#ifdef WITH_OPENSUBDIV + OsdData osd_data; + bool need_packed_patch_table = false; + + if(subdivision_type == SUBDIVISION_CATMULL_CLARK) { + osd_data.build_from_mesh(this); + } + else +#endif + { + /* force linear subdivision if OpenSubdiv is unavailable to avoid + * falling into catmull-clark code paths by accident + */ + subdivision_type = SUBDIVISION_LINEAR; + + /* force disable attribute subdivision for same reason as above */ + foreach(Attribute& attr, subd_attributes.attributes) { + attr.flags &= ~ATTR_SUBDIVIDED; + } + } + + int num_faces = subd_faces.size(); + + Attribute *attr_vN = subd_attributes.find(ATTR_STD_VERTEX_NORMAL); + float3* vN = attr_vN->data_float3(); + + for(int f = 0; f < num_faces; f++) { + SubdFace& face = subd_faces[f]; + + if(face.is_quad()) { + /* quad */ + QuadDice::SubPatch subpatch; + + LinearQuadPatch quad_patch; +#ifdef WITH_OPENSUBDIV + OsdPatch osd_patch(&osd_data); + + if(subdivision_type == SUBDIVISION_CATMULL_CLARK) { + osd_patch.patch_index = face.ptex_offset; + + subpatch.patch = &osd_patch; + } + else +#endif + { + float3 *hull = quad_patch.hull; + float3 *normals = quad_patch.normals; + + quad_patch.patch_index = face.ptex_offset; + + for(int i = 0; i < 4; i++) { + hull[i] = verts[subd_face_corners[face.start_corner+i]]; + } + + if(face.smooth) { + for(int i = 0; i < 4; i++) { + normals[i] = vN[subd_face_corners[face.start_corner+i]]; + } + } + else { + float3 N = face.normal(this); + for(int i = 0; i < 4; i++) { + normals[i] = N; + } + } + + swap(hull[2], hull[3]); + swap(normals[2], normals[3]); + + subpatch.patch = &quad_patch; + } + + subpatch.patch->shader = face.shader; + + /* Quad faces need to be split at least once to line up with split ngons, we do this + * here in this manner because if we do it later edge factors may end up slightly off. + */ + subpatch.P00 = make_float2(0.0f, 0.0f); + subpatch.P10 = make_float2(0.5f, 0.0f); + subpatch.P01 = make_float2(0.0f, 0.5f); + subpatch.P11 = make_float2(0.5f, 0.5f); + split->split_quad(subpatch.patch, &subpatch); + + subpatch.P00 = make_float2(0.5f, 0.0f); + subpatch.P10 = make_float2(1.0f, 0.0f); + subpatch.P01 = make_float2(0.5f, 0.5f); + subpatch.P11 = make_float2(1.0f, 0.5f); + split->split_quad(subpatch.patch, &subpatch); + + subpatch.P00 = make_float2(0.0f, 0.5f); + subpatch.P10 = make_float2(0.5f, 0.5f); + subpatch.P01 = make_float2(0.0f, 1.0f); + subpatch.P11 = make_float2(0.5f, 1.0f); + split->split_quad(subpatch.patch, &subpatch); + + subpatch.P00 = make_float2(0.5f, 0.5f); + subpatch.P10 = make_float2(1.0f, 0.5f); + subpatch.P01 = make_float2(0.5f, 1.0f); + subpatch.P11 = make_float2(1.0f, 1.0f); + split->split_quad(subpatch.patch, &subpatch); + } + else { + /* ngon */ +#ifdef WITH_OPENSUBDIV + if(subdivision_type == SUBDIVISION_CATMULL_CLARK) { + OsdPatch patch(&osd_data); + + patch.shader = face.shader; + + for(int corner = 0; corner < face.num_corners; corner++) { + patch.patch_index = face.ptex_offset + corner; + + split->split_quad(&patch); + } + } + else +#endif + { + float3 center_vert = make_float3(0.0f, 0.0f, 0.0f); + float3 center_normal = make_float3(0.0f, 0.0f, 0.0f); + + float inv_num_corners = 1.0f/float(face.num_corners); + for(int corner = 0; corner < face.num_corners; corner++) { + center_vert += verts[subd_face_corners[face.start_corner + corner]] * inv_num_corners; + center_normal += vN[subd_face_corners[face.start_corner + corner]] * inv_num_corners; + } + + for(int corner = 0; corner < face.num_corners; corner++) { + LinearQuadPatch patch; + float3 *hull = patch.hull; + float3 *normals = patch.normals; + + patch.patch_index = face.ptex_offset + corner; + + patch.shader = face.shader; + + hull[0] = verts[subd_face_corners[face.start_corner + mod(corner + 0, face.num_corners)]]; + hull[1] = verts[subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)]]; + hull[2] = verts[subd_face_corners[face.start_corner + mod(corner - 1, face.num_corners)]]; + hull[3] = center_vert; + + hull[1] = (hull[1] + hull[0]) * 0.5; + hull[2] = (hull[2] + hull[0]) * 0.5; + + if(face.smooth) { + normals[0] = vN[subd_face_corners[face.start_corner + mod(corner + 0, face.num_corners)]]; + normals[1] = vN[subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)]]; + normals[2] = vN[subd_face_corners[face.start_corner + mod(corner - 1, face.num_corners)]]; + normals[3] = center_normal; + + normals[1] = (normals[1] + normals[0]) * 0.5; + normals[2] = (normals[2] + normals[0]) * 0.5; + } + else { + float3 N = face.normal(this); + for(int i = 0; i < 4; i++) { + normals[i] = N; + } + } + + split->split_quad(&patch); + } + } + } + } + + /* interpolate center points for attributes */ + foreach(Attribute& attr, subd_attributes.attributes) { +#ifdef WITH_OPENSUBDIV + if(subdivision_type == SUBDIVISION_CATMULL_CLARK && attr.flags & ATTR_SUBDIVIDED) { + if(attr.element == ATTR_ELEMENT_CORNER || attr.element == ATTR_ELEMENT_CORNER_BYTE) { + /* keep subdivision for corner attributes disabled for now */ + attr.flags &= ~ATTR_SUBDIVIDED; + } + else { + osd_data.subdivide_attribute(attr); + + need_packed_patch_table = true; + continue; + } + } +#endif + + char* data = attr.data(); + size_t stride = attr.data_sizeof(); + int ngons = 0; + + switch(attr.element) { + case ATTR_ELEMENT_VERTEX: { + for(int f = 0; f < num_faces; f++) { + SubdFace& face = subd_faces[f]; + + if(!face.is_quad()) { + char* center = data + (verts.size() - num_subd_verts + ngons) * stride; + attr.zero_data(center); + + float inv_num_corners = 1.0f / float(face.num_corners); + + for(int corner = 0; corner < face.num_corners; corner++) { + attr.add_with_weight(center, + data + subd_face_corners[face.start_corner + corner] * stride, + inv_num_corners); + } + + ngons++; + } + } + } break; + case ATTR_ELEMENT_VERTEX_MOTION: { + // TODO(mai): implement + } break; + case ATTR_ELEMENT_CORNER: { + for(int f = 0; f < num_faces; f++) { + SubdFace& face = subd_faces[f]; + + if(!face.is_quad()) { + char* center = data + (subd_face_corners.size() + ngons) * stride; + attr.zero_data(center); + + float inv_num_corners = 1.0f / float(face.num_corners); + + for(int corner = 0; corner < face.num_corners; corner++) { + attr.add_with_weight(center, + data + (face.start_corner + corner) * stride, + inv_num_corners); + } + + ngons++; + } + } + } break; + case ATTR_ELEMENT_CORNER_BYTE: { + for(int f = 0; f < num_faces; f++) { + SubdFace& face = subd_faces[f]; + + if(!face.is_quad()) { + uchar* center = (uchar*)data + (subd_face_corners.size() + ngons) * stride; + + float inv_num_corners = 1.0f / float(face.num_corners); + float4 val = make_float4(0.0f, 0.0f, 0.0f, 0.0f); + + for(int corner = 0; corner < face.num_corners; corner++) { + for(int i = 0; i < 4; i++) { + val[i] += float(*(data + (face.start_corner + corner) * stride + i)) * inv_num_corners; + } + } + + for(int i = 0; i < 4; i++) { + center[i] = uchar(min(max(val[i], 0.0f), 255.0f)); + } + + ngons++; + } + } + } break; + default: break; + } + } + +#ifdef WITH_OPENSUBDIV + /* pack patch tables */ + if(need_packed_patch_table) { + delete patch_table; + patch_table = new PackedPatchTable; + patch_table->pack(osd_data.patch_table); + } +#endif +} + +CCL_NAMESPACE_END + |