Skip to content
  • 首页
  • 留言
  • 关于

Share the joys of programming and technology

在mapbox中实现3dtiles的加载和绘制(移动端)

在mapbox中实现3dtiles的加载和绘制(移动端)

2022年10月15日 liyanliang Comments 0 Comment

一、解析3dtiles数据和提取gltf模型文件

可参照这篇文章:解析3dTiles数据和提取b3dm模型文件

1.解析tileset.json文件

可以参照 cesium.js 或者 cesium-native,这两个库都是借用第三方库rapidjson进行解析。

以下是cesium-native的示例代码:

​
namespace {
​
std::optional<BoundingVolume> getBoundingVolumeProperty(
    const rapidjson::Value& tileJson,
    const std::string& key);
​
TilesSelection::UriType getTileType(const std::string& uri){
    std::string::size_type pos = uri.rfind('.');
    std::string strExt = uri.substr(pos == std::string::npos ? uri.length() : pos+1);
    
    TilesSelection::UriType uriType = TilesSelection::UriType::Json;
    
    if (strExt.compare("json") == 0) {
        uriType = TilesSelection::UriType::Json;
    } else if (strExt.compare("b3dm") == 0){
        uriType = TilesSelection::UriType::B3dm;
    } else {
        assert(0);
        Log::Error("Invalid ext type.");
    }
​
    return uriType;
}
} // namespace
​
bool LoadTileFromJson::execute(
    Tile& tile,
    const rapidjson::Value& tileJson,
    const math::mat4& parentTransform,
    TileRefine parentRefine,
    Tileset* pTileset) {
    if (!tileJson.IsObject()) {
        assert(0);
        return false;
    }
    
    tile.setTileset(pTileset);
    
    const std::optional<math::mat4> tileTransform =
    JsonHelpers::getTransformProperty(tileJson, "transform");
    math::mat4 transform =
    parentTransform * tileTransform.value_or(math::mat4(1.0));
    tile.setTransform(transform);
    
    std::optional<double> geometricError =
    JsonHelpers::getScalarProperty(tileJson, "geometricError");
    if (!geometricError) {
        geometricError = tile.getNonZeroGeometricError();
        Log::Error(
                   "Tile did not contain a geometricError. "
                   "Using half of the parent tile's geometric error.\n");
    }
    
    const auto contentIt = tileJson.FindMember("content");
    const auto childrenIt = tileJson.FindMember("children");
    
    if (contentIt != tileJson.MemberEnd() && contentIt->value.IsObject()) {
        auto uriIt = contentIt->value.FindMember("uri");
        if (uriIt == contentIt->value.MemberEnd() || !uriIt->value.IsString()) {
            uriIt = contentIt->value.FindMember("url");
        }
        
        tile.setEmptyContentState(false);
        
        if (uriIt != contentIt->value.MemberEnd() && uriIt->value.IsString()) {
            std::string strUri = uriIt->value.GetString();
            size_t pos = 0;
            
            if (std::string::npos != (pos = strUri.find("./")) ){
                strUri.erase(pos, 2);
            }
            TileID contentUri = tile.getbaseUrl() + "/" + strUri;
            tile.setTileID(contentUri);
            tile.setType(getTileType(contentUri));
            tile.setState(Tile::LoadState::Unloaded);
        }
        
        std::optional<BoundingVolume> contentBoundingVolume =
        getBoundingVolumeProperty(contentIt->value, "boundingVolume");
        if (contentBoundingVolume) {
            tile.setContentBoundingVolume(transformBoundingVolume(transform, contentBoundingVolume.value()));
        }
    } else {
        TileID emptyTile = "/emptyTile-" + Tool::toString(geometricError.value());
        tile.setTileID(emptyTile);
        tile.setEmptyContentState(true);
        tile.setState(Tile::LoadState::Done);
    }
    
    std::optional<BoundingVolume> boundingVolume =
    getBoundingVolumeProperty(tileJson, "boundingVolume");
    if (!boundingVolume) {
        assert(0);
        Log::Error("Tile did not contain a boundingVolume\n");
        return false;
    }
    
    tile.setBoundingVolume(
                           transformBoundingVolume(transform, boundingVolume.value()));
    const math::double3 scale = math::double3(
                                              length(transform[0]),
                                              length(transform[1]),
                                              length(transform[2]));
    const double maxScaleComponent =
    std::max(scale.x, std::max(scale.y, scale.z));
    tile.setGeometricError(geometricError.value() * maxScaleComponent);
    
    std::optional<BoundingVolume> viewerRequestVolume =
    getBoundingVolumeProperty(tileJson, "viewerRequestVolume");
    if (viewerRequestVolume) {
        tile.setViewerRequestVolume(
                                    transformBoundingVolume(transform, viewerRequestVolume.value()));
    }
    
    const auto refineIt = tileJson.FindMember("refine");
    if (refineIt != tileJson.MemberEnd() && refineIt->value.IsString()) {
        std::string refine = refineIt->value.GetString();
        if (refine == "REPLACE") {
            tile.setRefine(TileRefine::Replace);
        } else if (refine == "ADD") {
            tile.setRefine(TileRefine::Add);
        } else {
            std::string refineUpper = refine;
            std::transform(
                           refineUpper.begin(),
                           refineUpper.end(),
                           refineUpper.begin(),
                           [](unsigned char c) -> unsigned char {
                               return static_cast<unsigned char>(std::toupper(c));
                           });
            if (refineUpper == "REPLACE" || refineUpper == "ADD") {
                Log::Warning(
                             "Tile refine value %s should be uppercase: %s\n",
                             refine.c_str(),
                             refineUpper.c_str());
                tile.setRefine(
                               refineUpper == "REPLACE" ? TileRefine::Replace : TileRefine::Add);
            } else {
                Log::Warning(
                             "Tile contained an unknown refine value: %s\n",
                             refine.c_str());
            }
        }
    } else {
        tile.setRefine(parentRefine);
    }
    
    if (childrenIt->value.IsArray()) {
        const auto& childrenJson = childrenIt->value;
        std::vector<Tile*>& childTiles = tile.getChildren();
        
        for (rapidjson::SizeType i = 0; i < childrenJson.Size(); ++i) {
            const auto& childJson = childrenJson[i];
            Tile* child = new Tile();
            child->setParent(&tile);
            child->setbaseUrl(tile.getbaseUrl());
            child->_depth = tile._depth + 1;
            bool result = LoadTileFromJson::execute(
                                                    *child,
                                                    childJson,
                                                    transform,
                                                    tile.getRefine(),
                                                    pTileset);
            
            if (result) {
                Tileset* pTileset = tile.getTileset();
                ++pTileset->_statistics.numberOfTilesTotal;
                childTiles.push_back(child);
            } else {
                Utility::Tool::deletePtr(child);
            }
            
        } // end for
    } // end if
    
    return true;
}
​
namespace {
​
std::optional<BoundingVolume> getBoundingVolumeProperty(
    const rapidjson::Value& tileJson,
    const std::string& key) {
  const auto bvIt = tileJson.FindMember(key.c_str());
  if (bvIt == tileJson.MemberEnd() || !bvIt->value.IsObject()) {
    return std::nullopt;
  }
​
  const auto extensionsIt = bvIt->value.FindMember("extensions");
  if (extensionsIt != bvIt->value.MemberEnd() &&
      extensionsIt->value.IsObject()) {
      assert(0);
      return std::nullopt;
  }
​
  const auto boxIt = bvIt->value.FindMember("box");
  if (boxIt != bvIt->value.MemberEnd() && boxIt->value.IsArray() &&
      boxIt->value.Size() >= 12) {
    const auto& a = boxIt->value.GetArray();
    for (rapidjson::SizeType i = 0; i < 12; ++i) {
      if (!a[i].IsNumber()) {
        return std::nullopt;
      }
    }
    return OrientedBoundingBox(
        math::double3(a[0].GetDouble(), a[1].GetDouble(), a[2].GetDouble()),
        math::mat3(
            a[3].GetDouble(),
            a[4].GetDouble(),
            a[5].GetDouble(),
            a[6].GetDouble(),
            a[7].GetDouble(),
            a[8].GetDouble(),
            a[9].GetDouble(),
            a[10].GetDouble(),
            a[11].GetDouble()));
  }
​
  const auto regionIt = bvIt->value.FindMember("region");
  if (regionIt != bvIt->value.MemberEnd() && regionIt->value.IsArray() &&
      regionIt->value.Size() >= 6) {
    const auto& a = regionIt->value;
    for (rapidjson::SizeType i = 0; i < 6; ++i) {
      if (!a[i].IsNumber()) {
        return std::nullopt;
      }
    }
      
      assert(0);
      return std::nullopt;
  }
​
  const auto sphereIt = bvIt->value.FindMember("sphere");
  if (sphereIt != bvIt->value.MemberEnd() && sphereIt->value.IsArray() &&
      sphereIt->value.Size() >= 4) {
    const auto& a = sphereIt->value;
    for (rapidjson::SizeType i = 0; i < 4; ++i) {
      if (!a[i].IsNumber()) {
        return std::nullopt;
      }
    }
    return BoundingSphere(
        math::double3(a[0].GetDouble(), a[1].GetDouble(), a[2].GetDouble()),
        a[3].GetDouble());
  }
​
  return std::nullopt;
}
​
} // namespace

2.解析b3dm模型文件

image-20220416155052757

如上图所示,一个b3dm模型中,存储了很多信息,我们的目标是提取出glTF二进制文件,所以需要出去表头字节数:

    uint32_t byteOffset = 0;
    std::string err;
    
    if (!tilegltf::util::getGlbByteOffset(&err, (unsigned char*)data->data(), (unsigned int)data->size(), byteOffset)){
        mbgl::Log::Error(Event::Style, "Failed to get glb byte offset: %s \n", err.c_str());
        assert(0);
        return;
    }

    uint8_t* pData = (uint8_t*)data->data() + byteOffset; // delete b3dm head byte
    uint32_t dataLen = static_cast<unsigned int>(data->size()) - byteOffset;
    
    std::shared_ptr<const std::string> resData = std::make_shared<const std::string>(reinterpret_cast<const char *>(pData), dataLen);
bool getGlbByteOffset(std::string *err,
                                    unsigned char *bytes,
                                    unsigned int size,
                                    unsigned int& byteOffset) {
    if (size < 28) {
       if (err) {
         (*err) = "Too short data size for b3dm Binary.";
       }
       return false;
     }

     if (bytes[0] == 'b' && bytes[1] == '3' && bytes[2] == 'd' &&
         bytes[3] == 'm') {
       // ok
     } else {
       if (err) {
         (*err) = "Invalid magic.";
       }
       return false;
     }

     unsigned int version;                     // 4 bytes
     unsigned int byteLength;                  // 4 bytes
     unsigned int featureTableJSONByteLength;  // 4 bytes
     unsigned int featureTableBinaryByteLength;// 4 bytes;
     unsigned int batchTableJSONByteLength;    // 4 bytes
     unsigned int batchTableBinaryByteLength;  // 4 bytes;

     // @todo { Endian swap for big endian machine. }
     memcpy(&version, bytes + 4, 4);
     memcpy(&byteLength, bytes + 8, 4);
     memcpy(&featureTableJSONByteLength, bytes + 12, 4);
     memcpy(&featureTableBinaryByteLength, bytes + 16, 4);
     memcpy(&batchTableJSONByteLength, bytes + 20, 4);
     memcpy(&batchTableBinaryByteLength, bytes + 24, 4);

     if ((byteLength != size) || (byteLength < 1) ) {
       if (err) {
         (*err) = "Invalid b3dm binary.";
       }
       return false;
     }

    byteOffset = 28 + featureTableJSONByteLength + featureTableBinaryByteLength +
                batchTableJSONByteLength + batchTableBinaryByteLength;

     return true;
}

二、绘制glTF模型

1.坐标转换

3dtiles的坐标为墨卡托坐标系,单位是米,需要转换为mapbox的坐标系,单位是像素:

    //米转像素
    const double WEBMERCATOR_EXTENT = 20037508.3427892;
    double worldSizeMeter = WEBMERCATOR_EXTENT * 2.0; // 米的世界范围
    double scale = Projection::worldSize(state.getScale()) / worldSizeMeter;
    matrix::scale(_overlayParamter.modelMat, _overlayParamter.modelMat, scale , scale , 1.0);
    
    // 转换到mapbox坐标系(原点在左上角)
    mat4 matMapbox;
    matrix::identity(matMapbox);
    matMapbox[12] = WEBMERCATOR_EXTENT;
    matMapbox[13] = WEBMERCATOR_EXTENT;
    matrix::scale(matMapbox, matMapbox, 1.0, -1.0, 1.0);
    matrix::multiply(_overlayParamter.modelMat, _overlayParamter.modelMat, matMapbox);
    
    //将模型坐标转换为世界坐标(原点在center)
    matrix::multiply(_overlayParamter.modelMat, _overlayParamter.modelMat, _outMat);

2.mapbox添加GL_UNSIGNED_INT索引类型

mapbox中OpenGL的绘制,默认的顶点索引类型为UShort,不支持GL_UNSIGNED_INT的索引类型,需要添加GL_UNSIGNED_INT类型:

void Context::draw(const gfx::DrawMode& drawMode,
                   std::size_t indexOffset,
                   std::size_t indexLength,
                   gfx::ElementIndexDataType indexDataType) {
    switch (drawMode.type) {
    case gfx::DrawModeType::Points:
#if not MBGL_USE_GLES2
        // In OpenGL ES 2, the point size is set in the vertex shader.
        pointSize = drawMode.size;
#endif // MBGL_USE_GLES2
        break;
    case gfx::DrawModeType::Lines:
    case gfx::DrawModeType::LineLoop:
    case gfx::DrawModeType::LineStrip:
        lineWidth = drawMode.size;
        break;
    default:
        break;
    }

    auto byteSize = [](platform::GLenum value)->std::size_t{
        switch (value) {
            case GL_UNSIGNED_BYTE: return sizeof(uint8_t);
            case GL_UNSIGNED_SHORT: return sizeof(uint16_t);
            case GL_UNSIGNED_INT: return sizeof(uint32_t);
        }
        return sizeof(uint16_t);
    };
    
    platform::GLenum dataType = Enum<gfx::ElementIndexDataType>::to(indexDataType);
    std::size_t bytesize = byteSize(dataType);
    
    MBGL_CHECK_ERROR(glDrawElements(
        Enum<gfx::DrawModeType>::to(drawMode.type),
        static_cast<GLsizei>(indexLength),
        dataType,
        reinterpret_cast<GLvoid*>(bytesize * indexOffset)));
}
gfx::ElementIndexDataType mapElementIndexDataType(int32_t type)
{
    switch (type)
    {
        case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
            return gfx::ElementIndexDataType::UShort;
        case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
            return gfx::ElementIndexDataType::UInt;
    }
    
    assert(false);
    return gfx::ElementIndexDataType::UShort;
}

三、在mapbox中添加3dtiles的layer

1.在mapbox中添加3dtiles的layer

3dtiles layer UML类图01

2.mapbox添加style layer的代码流程图

03.mapbox添加style layer的代码流程图

3.mapbox添加render layer代码流程图

04.mapbox添加render layer代码流程图`

4.mapbox请求数据代码流程图

05.mapbox请求数据代码流程图

四、异步加载

1.在class Resource中添加3dtiles的类型

class Resource {
public:
enum Kind : uint8_t {
Unknown = 0,
Style,
Source,
Tile,
Glyphs,
SpriteImage,
SpriteJSON,
Image,
FX3dTile
};
Resource Resource::FX3dTile(const std::string& url,
                        LoadingMethod loadingMethod) {
    return Resource {
        Resource::Kind::FX3dTile,
        url,
        loadingMethod
    };
}

2.网络请求

template <typename T>
TileLoader<T>::TileLoader(T& tile_,
                          const FX3dTileID& id,
                          const TileParameters& parameters)
    : tile(tile_),
      necessity(TileNecessity::Optional),
      resource(Resource::FX3dtilesTile(id)), // 网络
//      resource(Resource::FX3dtilesTile("file://"+id)), // 本地
      fileSource(parameters.fileSource){
      assert(!request);
    if (fileSource->supportsCacheOnlyRequests()) {
        // When supported, the first request is always optional, even if the TileLoader
        // is marked as required. That way, we can let the first optional request continue
        // to load when the TileLoader is later changed from required to optional. If we
        // started out with a required request, we'd have to cancel everything, including the
        // initial optional part of the request.
        loadFromCache();
    } else if (necessity == TileNecessity::Required) {
        // When the file source doesn't support cache-only requests, and we definiitely need this
        // data, we can start out with a network request immediately.
        loadFromNetwork();
    } else {
        // When the FileSource doesn't support cache-only requests, we do nothing until the
        // data is definitely required.
    }
}
Resource Resource::FX3dtilesTile(const std::string& url) {
    return Resource {
        Resource::Kind::FX3dTile,
        url
    };
}

五、几何误差

1.规范中的定义

image-20220608154745221
image-20220608160501365
image-20220608160641795
image-20220608161623588

2.计算SSE

sseDenominator = 2.0 * Math.tan(0.5 * frustum._fovy);
double ViewState::computeScreenSpaceError(
    double geometricError,
    double distance) const noexcept {
  // Avoid divide by zero when viewer is inside the tile
  distance = glm::max(distance, 1e-7);
  const double sseDenominator = this->_sseDenominator;
  return (geometricError * this->_viewportSize.y) / (distance * sseDenominator);
}

关于tile到相机的distance的计算:

double OrientedBoundingBox::computeDistanceSquaredToPosition(
    const glm::dvec3& position) const noexcept {
  const glm::dvec3 offset = position - this->getCenter();

  const glm::dmat3& halfAxes = this->getHalfAxes();
  glm::dvec3 u = halfAxes[0];
  glm::dvec3 v = halfAxes[1];
  glm::dvec3 w = halfAxes[2];

  const double uHalf = glm::length(u);
  const double vHalf = glm::length(v);
  const double wHalf = glm::length(w);

  u /= uHalf;
  v /= vHalf;
  w /= wHalf;

  const glm::dvec3 pPrime(
      glm::dot(offset, u),
      glm::dot(offset, v),
      glm::dot(offset, w));

  double distanceSquared = 0.0;
  double d;

  if (pPrime.x < -uHalf) {
    d = pPrime.x + uHalf;
    distanceSquared += d * d;
  } else if (pPrime.x > uHalf) {
    d = pPrime.x - uHalf;
    distanceSquared += d * d;
  }

  if (pPrime.y < -vHalf) {
    d = pPrime.y + vHalf;
    distanceSquared += d * d;
  } else if (pPrime.y > vHalf) {
    d = pPrime.y - vHalf;
    distanceSquared += d * d;
  }

  if (pPrime.z < -wHalf) {
    d = pPrime.z + wHalf;
    distanceSquared += d * d;
  } else if (pPrime.z > wHalf) {
    d = pPrime.z - wHalf;
    distanceSquared += d * d;
  }

  return distanceSquared;
}

3.判断是否满足sse

maximumScreenSpaceError = 16.

bool GltfManager::meetsSse(
    const mbgl::TransformState& state,
    const Tile& tile,
    const double distance) const {

    const double maxScaleComponent = util::getMaxScaleComponent(_fx3dtilesParamter.mercatorToMapboxMat);
        
    const double geometricError = tile.geometricError * maxScaleComponent;
    // Does this tile meet the screen-space error?
    const double sse =
        computeScreenSpaceError(state, geometricError, distance);
  return sse < maximumScreenSpaceError;
}

六、3dtiles的tileset的遍历

此部分可以参考cesium-native.

21.几何误差代码流程

相关函数的代码实现:

FX3DTilesetTraversal::FX3DTilesetTraversal(){}

FX3DTilesetTraversal::~FX3DTilesetTraversal(){}

bool FX3DTilesetTraversal::selectTiles(
        std::shared_ptr<tilegltf::Tileset> tileset,
        const mbgl::TransformState& state){
    if (tileset == nullptr) {
        return false;
    }
    tileset->clearLoadQueue();
    initializeParameter(state);
    
    Tile* pRootTile = tileset->pRootTile.get();
    visitTileIfNeeded(*tileset, state, 0, false, *pRootTile);
    
    tileset->sortLoadQueue();
    
    return true;
}

void FX3DTilesetTraversal::initializeParameter(const mbgl::TransformState& state){
    state.getProjectionMatrix(_fx3dtilesParamter.projMat);
    state.getViewMatrix(_fx3dtilesParamter.viewMat);
    
    // tileModelmat
    math::mat4 modelMat;
    state.getModelMatrix(modelMat);
    
    //convert to mapbox(unit:pixel)
    math::mat4 mercatorToMapbox;
    util::getMercatorToMapboxMat(mercatorToMapbox, state);

    _fx3dtilesParamter.modelMat = _fx3dtilesParamter.modelMat * mercatorToMapbox;
    _fx3dtilesParamter.modelMat = modelMat * _fx3dtilesParamter.modelMat;
    
    // cameraPos
    math::mat4 invViewMat;
    invViewMat *= inverse(_fx3dtilesParamter.viewMat);
    math::double4 cameraPos{0.0, 0.0, 0.0, 1.0};
    cameraPos = invViewMat * cameraPos;
    _fx3dtilesParamter.cameraPos = cameraPos.xyz;
    
    // camFrustum
    math::mat4 projMat;
    state.getProjectionMatrix(projMat);
    
    math::mat4 invProjMat = inverse(projMat);
    
    _fx3dtilesParamter.camFrustum = fromClipMatrix(projMat, invProjMat);
}

void FX3DTilesetTraversal::visitTileIfNeeded(
    tilegltf::Tileset& tileset,
    const mbgl::TransformState& state,
    uint32_t depth,
    bool ancestorMeetsSse,
    Tile& tile) {

    // whether we should visit this tile
     bool shouldVisit = true;

    if (!isVisibleFromCamera(tile.boundingVolume)) {
      // frustum culling is enabled so we shouldn't visit this off-screen tile
      shouldVisit = false;
    }
    
    if (!shouldVisit) {
        return;
    }
    
    return this->visitTile(
                    tileset,
                    state,
                    depth,
                    ancestorMeetsSse,
                    tile);
}

void FX3DTilesetTraversal::visitTile(
            tilegltf::Tileset& tileset,
            const mbgl::TransformState& state,
            uint32_t depth,
            bool ancestorMeetsSse,
            Tile& tile) {
    
    const double distance = std::sqrt(max(
              computeDistanceSquaredToBoundingVolume(tile.boundingVolume), 0.0));
    
    // If this is a leaf tile, render it
     if (isLeaf(tile)) {
       tileset.addTileToLoadQueue(tile, distance);
       return;
     }
    
    const bool unconditionallyRefine = tile.getUnconditionallyRefine();
    const bool bMeetsSse = meetsSse(state, tile, distance);
    
    bool wantToRefine = unconditionallyRefine || (!bMeetsSse && !ancestorMeetsSse);
    
    if (!wantToRefine) {
        if (bMeetsSse && !ancestorMeetsSse) {
            tileset.addTileToLoadQueue(tile, distance);
        }
        return;
    }
    
    // Refine
    if (tile.hasContent()) {
        
        if (tile.content->uriType == Content::UriType::Json) {
            tile.content->tileset = tileset.tilesetRequestManager->getTileset(tile.id);
            
            if (tile.content->tileset != nullptr){
                Tile* pRootTile = tile.content->tileset->pRootTile.get();
                
                this->visitTileIfNeeded(
                         tileset,
                         state,
                         0,
                         false,
                         *pRootTile);
            }
            else{
                tileset.addTilesetToLoadQueue(tile, distance);
            }
        }
        else{ // B3dm
            loadAndRenderAdditiveRefinedTile(tileset, tile, distance);
        }
    }
    
    visitVisibleChildren(tileset, state, depth, ancestorMeetsSse, tile);
    return;
}

bool FX3DTilesetTraversal::isLeaf(Tile& tile){
    if (tile.content != nullptr && tile.content->uriType == Content::UriType::B3dm) {
        return tile.isLeaf();
    }
    
    return false;
}

bool FX3DTilesetTraversal::visitVisibleChildren(
    tilegltf::Tileset& tileset,
    const mbgl::TransformState& state,
    uint32_t depth,
    bool ancestorMeetsSse,
    Tile& tile) {
    
    for (Tile* child : tile.children) {
         this->visitTileIfNeeded(
                  tileset,
                  state,
                  depth + 1,
                  ancestorMeetsSse,
                  *child);
    }
    
    return true;
}

bool FX3DTilesetTraversal::isVisibleFromCamera(const BoundingVolume& boundingVolume) {

    math::mat4 viewModelMat = _fx3dtilesParamter.viewMat * _fx3dtilesParamter.modelMat;
    const BoundingVolume& viewBoudingBox1 = boundingVolume.transformBoundingVolume(_fx3dtilesParamter.modelMat);

    const BoundingVolume& viewBoudingBox = boundingVolume.transformBoundingVolume(viewModelMat);
    
    bool intersect = util::intersectsOBB(_fx3dtilesParamter.camFrustum, viewBoudingBox);
    
    return intersect;
}

double FX3DTilesetTraversal::computeDistanceSquaredToBoundingVolume(
    const BoundingVolume& boundingVolume) const {
    
    const BoundingVolume& boundingVolumeTemp = boundingVolume.transformBoundingVolume(_fx3dtilesParamter.modelMat);
    double distance = boundingVolumeTemp.computeDistanceSquaredToPosition(_fx3dtilesParamter.cameraPos);
        
    return distance;
}

bool FX3DTilesetTraversal::meetsSse(
    const mbgl::TransformState& state,
    const Tile& tile,
    const double distance) const {

    const double maxScaleComponent = util::getMaxScaleComponent(_fx3dtilesParamter.modelMat);
        
    const double geometricError = tile.geometricError * maxScaleComponent;
  
    const double sse =
        computeScreenSpaceError(state, geometricError, distance);

  return sse < maximumScreenSpaceError;
}

double FX3DTilesetTraversal::computeScreenSpaceError(
    const mbgl::TransformState& state,
    const double geometricError,
    double distance) const {
        
  // Avoid divide by zero when viewer is inside the tile
  distance = max(distance, 1e-7);
        
  const double sseDenominator = 2.0 * std::tan(0.5 * state.getFieldOfView());
        
  Size size = state.getSize();
  double viewportHeight = size.height;
        
  return (geometricError * viewportHeight) / (distance * sseDenominator);
}

const Tile* FX3DTilesetTraversal::getRenderedTile(const FX3dTileID& tileID) const {
    auto it = renderedTiles.find(tileID);
    return it != renderedTiles.end() ? &it->second.get() : nullptr;
}

bool FX3DTilesetTraversal::loadAndRenderAdditiveRefinedTile(tilegltf::Tileset& tileset, Tile& tile, const double distance) {
  // If this tile uses additive refinement, we need to render this tile in
  // addition to its children.
  if (tile.refine == Tile::Refine::Add) {
      tileset.addTileToLoadQueue(tile, distance);
  }
  return true;
}

七、缓存处理

缓存处理使用比较简单的方式,仍是按照tile的数量来确定缓存的大小。

当一个tile为请求下来是,先查看其children是否都已经加载,若全都已经加载,则使用children来cover此tile,否则使用parent.

void FX3dtilesPyramid::update(const std::vector<Immutable<style::LayerProperties>>& layers,
                         const bool needsRendering,
                         const bool needsRelayout,
                         const TileParameters& parameters,
                         const std::string& filePath,
                         std::function<std::unique_ptr<Tile> (const FX3dTileID&, const math::mat4&)> createTile) {
    // If we need a relayout, abandon any cached tiles; they're now stale.
    if (needsRelayout) {
        cache.clear();
    }
    
    // If we're not going to render anything, move our existing tiles into
    // the cache (if they're not stale) or abandon them, and return.
    if (!needsRendering) {
        if (!needsRelayout) {
            for (auto& entry : tiles) {
                // These tiles are invisible, we set optional necessity
                // for them and thus suppress network requests on
                // tiles expiration (see `OnlineFileRequest`).
                entry.second->setNecessity(TileNecessity::Optional);
                cache.add(entry.first, std::move(entry.second));
            }
        }

        tiles.clear();
        renderedTiles.clear();
        return;
    }
    
//    const std::string& fileName = filePath + "/tileset.json";  // 本地
    const std::string& fileName = filePath;  // 网络
    if(tileset == nullptr) {
        if (tilesetRequester == nullptr){
            math::mat4 parentTransform;
            tilesetRequester = new lkgl::TilesetRequester(fileName, parentTransform, parameters.fileSource);
            tilesetRequester->load();
        }

         if (!tilesetRequester->loaded){
             return;
         }
        
        tileset = tilesetRequester->getTileset();
        tileset->setFileSource(parameters.fileSource);
    }
    
    tilegltf::FX3DTilesetTraversal traversal;
    traversal.selectTiles(tileset, parameters.transformState);
    
    tileset->requestTilesets();

    // Stores a list of all the tiles that we're definitely going to retain. There are two
    // kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in
    // use because they're still loading. In addition to that, we also need to retain all tiles that
    // we're actively using, e.g. as a replacement for tile that aren't loaded yet.
    std::set<FX3dTileID> retain;
    
    auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void {
        if (retain.emplace(tile.fx3dtileId).second) {
            tile.setNecessity(necessity);
        }

        if (needsRelayout) {
            tile.setLayers(layers);
        }
    };
    auto getTileFn = [&](const FX3dTileID& tileID) -> Tile* {
        auto it = tiles.find(tileID);
        return it == tiles.end() ? nullptr : it->second.get();
    };

    auto createTileFn = [&](const FX3dTileID& tileID, const math::mat4& matrix) -> Tile* {
        
        std::unique_ptr<Tile> tile = cache.pop(tileID);
        if (!tile) {
            tile = createTile(tileID, matrix);
            if (tile) {
                tile->setObserver(observer);
                tile->setLayers(layers);
            }
        }
        if (!tile) {
            return nullptr;
        }
        return tiles.emplace(tileID, std::move(tile)).first->second.get();
    };
    
    auto previouslyRenderedTiles = std::move(renderedTiles);

    auto renderTileFn = [&](const FX3dTileID& tileID, Tile& tile) {
        addRenderTile(tileID, tile);
        previouslyRenderedTiles.erase(tileID); // Still rendering this tile, no need for special fading logic.
        tile.markRenderedIdeal();
    };
    
    std::set<const tilegltf::Tile*> parent3dTiles;
    auto parent3dTileFn = [&](const tilegltf::Tile* tile) -> void {
        parent3dTiles.emplace(tile);
    };
    
    renderedTiles.clear();
    
    const std::vector<tilegltf::LoadRecord>& idealTiles = tileset->getTileLoadQueue();
    
    algorithm::updateRenderablesFor3dtiles(getTileFn, createTileFn, retainTileFn,
                                           renderTileFn, parent3dTileFn, idealTiles);
    
    for (auto previouslyRenderedTile : previouslyRenderedTiles) {
        Tile& tile = previouslyRenderedTile.second;
        tile.markRenderedPreviously();
        if (tile.holdForFade()) {
            // Since it was rendered in the last frame, we know we have it
            // Don't mark the tile "Required" to avoid triggering a new network request
            retainTileFn(tile, TileNecessity::Optional);
            addRenderTile(previouslyRenderedTile.first, tile);
        }
    }
    
    cache.setSize(40);
    
    // Remove stale tiles. This goes through the (sorted!) tiles map and retain set in lockstep
    // and removes items from tiles that don't have the corresponding key in the retain set.
    {
        auto tilesIt = tiles.begin();
        auto retainIt = retain.begin();
        while (tilesIt != tiles.end()) {
            if (retainIt == retain.end() || tilesIt->first < *retainIt) {
                if (!needsRelayout) {
                    tilesIt->second->setNecessity(TileNecessity::Optional);
                    cache.add(tilesIt->first, std::move(tilesIt->second));
                }
                tiles.erase(tilesIt++);

            } else {
                if (!(*retainIt < tilesIt->first)) {
                    ++tilesIt;
                }
                ++retainIt;
            }
        }
    }
    
    // Remove parentRenderTile's children
    for (auto& parent : parent3dTiles){
        removeRenderedTile(parent);
    }

    // Initialize renderable tiles and update the contained layer render data.
    for (auto& entry : renderedTiles) {
        Tile& tile = entry.second;
        assert(tile.isRenderable());
        tile.usedByRenderedLayers = false;

        const bool holdForFade = tile.holdForFade();
        for (const auto& layerProperties : layers) {
            const auto* typeInfo = layerProperties->baseImpl->getTypeInfo();
            if (holdForFade && typeInfo->fadingTiles == LayerTypeInfo::FadingTiles::NotRequired) {
                continue;
            }
            tile.usedByRenderedLayers |= tile.layerPropertiesUpdated(layerProperties);
        }
    }
}
template <typename GetTileFn,
          typename CreateTileFn,
          typename RetainTileFn,
          typename RenderTileFn,
          typename Parent3dTileFn>
void updateRenderablesFor3dtiles(GetTileFn getTile,
                       CreateTileFn createTile,
                       RetainTileFn retainTile,
                       RenderTileFn renderTile,
                       Parent3dTileFn parent3dTileFn,
                       const std::vector<tilegltf::LoadRecord>& idealTiles) {
    std::unordered_set<FX3dTileID> checked;
    bool covered = true;

    for (auto& it : idealTiles) {
        auto tile = getTile(it.pTile->id);
        if (!tile) {
            tile = createTile(it.pTile->id, it.pTile->transform);

            if(tile == nullptr) {
                continue;
            }
        }

        // if (source has the tile and bucket is loaded) {
        if (tile->isRenderable()) {
            retainTile(*tile, TileNecessity::Required);
            renderTile(it.pTile->id, *tile);
        } else {
            
            // We are now attempting to load child and parent tiles.
            bool parentHasTriedOptional = tile->hasTriedCache();
            bool parentIsLoaded = tile->isLoaded();
            
            // The tile isn't loaded yet, but retain it anyway because it's an ideal tile.
            retainTile(*tile, TileNecessity::Required);
            
            covered = true;
            // Check all child tiles.
            for (const auto& childTile : it.pTile->children) {
                tile = getTile(childTile->id);
                if (tile && tile->isRenderable()) {
                    // do nothing
                } else {
                    // At least one child tile doesn't exist, so we are going to look for
                    // parents as well.
                    covered = false;
                    break;
                }
            }

            if (covered) {
                for (const auto& childTile : it.pTile->children) {
                    tile = getTile(childTile->id);
                    retainTile(*tile, TileNecessity::Required);
                    renderTile(childTile->id, *tile);
                }
            } else {
                const tilegltf::Tile* parent3dTile = it.pTile->getParent();
                // We couldn't find child tiles that entirely cover the ideal tile.
                while (parent3dTile != nullptr) {

                    const auto parentDataTileID = parent3dTile->id;

                    if (checked.find(parentDataTileID) != checked.end()) {
                        // Break parent tile ascent, this route has been checked by another child
                        // tile before.
                        break;
                    } else {
                        checked.emplace(parentDataTileID);
                    }

                    tile = getTile(parentDataTileID);

                    if (tile) {
                        if (!parentIsLoaded) {
                            // We haven't completed loading the child, so we only do an optional
                            // (cache) request in an attempt to quickly load data that we can show.
                            retainTile(*tile, TileNecessity::Optional);
                        } else {
                            // Now that we've checked the child and know for sure that we can't load
                            // it, we attempt to load the parent from the network.
                            retainTile(*tile, TileNecessity::Required);
                        }

                        // Save the current values, since they're the parent of the next iteration
                        // of the parent tile ascent loop.
                        parentHasTriedOptional = tile->hasTriedCache();
                        parentIsLoaded = tile->isLoaded();

                        if (tile->isRenderable()) {
                            renderTile(parentDataTileID, *tile);

                            parent3dTileFn(parent3dTile);

                            // Break parent tile ascent, since we found one.
                            break;
                        }
                    }

                    parent3dTile = parent3dTile->getParent();
                }

            } // end if !cover
            
        } // end if
        
    } // end for
} // end func
image-20220619101714572
image-20220619101740727

八、相机裁剪

3dtiles一般使用的是OBB包围盒,一般的做法是检测OBB是否都在视锥的六个平面之外。

遍历视椎体的6个面判断是否相交:

CullingResult
OrientedBoundingBox::intersectPlane(const Plane& plane) const noexcept {
  const glm::dvec3 normal = plane.getNormal();

  const glm::dmat3& halfAxes = this->getHalfAxes();
  const glm::dvec3& xAxisDirectionAndHalfLength = halfAxes[0];
  const glm::dvec3& yAxisDirectionAndHalfLength = halfAxes[1];
  const glm::dvec3& zAxisDirectionAndHalfLength = halfAxes[2];

  // plane is used as if it is its normal; the first three components are
  // assumed to be normalized
  const double radEffective = glm::abs(
                                  normal.x * xAxisDirectionAndHalfLength.x +
                                  normal.y * xAxisDirectionAndHalfLength.y +
                                  normal.z * xAxisDirectionAndHalfLength.z) +
                              glm::abs(
                                  normal.x * yAxisDirectionAndHalfLength.x +
                                  normal.y * yAxisDirectionAndHalfLength.y +
                                  normal.z * yAxisDirectionAndHalfLength.z) +
                              glm::abs(
                                  normal.x * zAxisDirectionAndHalfLength.x +
                                  normal.y * zAxisDirectionAndHalfLength.y +
                                  normal.z * zAxisDirectionAndHalfLength.z);

  const double distanceToPlane =
      ::glm::dot(normal, this->getCenter()) + plane.getDistance();

  if (distanceToPlane <= -radEffective) {
    // The entire box is on the negative side of the plane normal
    return CullingResult::Outside;
  }
  if (distanceToPlane >= radEffective) {
    // The entire box is on the positive side of the plane normal
    return CullingResult::Inside;
  }
  return CullingResult::Intersecting;
}

九、渲染

1.bucket

将模型数据上传至GPU

  void FX3dtilesBucket::upload(gfx::UploadPass &uploadPass) {
        if (uploaded) {
            return;
        }
        
        generateGPUBuffer(uploadPass);
        generateTextures(uploadPass);
        
        while (mCurrentMesh<(int16_t)model->meshes.size())
        {
            tinygltf::Mesh& mesh = model->meshes[mCurrentMesh];

            while (mCurrentPrimitive<(int16_t)mesh.primitives.size())
            {
                uploadStep(uploadPass,mCurrentMesh,mCurrentPrimitive);
                mCurrentPrimitive++;
            }
            mCurrentMesh++;
        }
        
        uploaded = true;

    }
void FX3dtilesBucket::uploadStep(gfx::UploadPass&uploadPass, int meshesIndex, int primitiveIndex)
{
    const tinygltf::Mesh& mesh = model->meshes[meshesIndex];
    const tinygltf::Primitive& primitive = mesh.primitives[primitiveIndex];
    FX3dtilesSegment *seg = new FX3dtilesSegment();
    tinygltf::Material& material = model->materials[primitive.material];
    if(material.alphaMode=="OPAQUE")
    {
        Segments.emplace_back(seg);
    }else
    {
        translucentSegments.emplace_back(seg);
    }
    const tinygltf::Accessor& indexAccessor = model->accessors[primitive.indices];
    assert(indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT);

    seg->indexOffset = 0;
    seg->indexLength = indexAccessor.count;
    seg->indexBufferView = indexAccessor.bufferView;
    
    std::vector<optional<gfx::AttributeBinding>> vecAttrBinding;
      vecAttrBinding.resize(3);
      for (auto attrIt : primitive.attributes)
      {
          auto& accessor = model->accessors[attrIt.second];
          auto& bufferView = model->bufferViews[accessor.bufferView];
          assert(accessor.bufferView < (int32_t)_gpuBufferViews.size());
          auto& gpuBufferView = _gpuBufferViews[accessor.bufferView];
          assert(gpuBufferView->vertexBuffer.has_value());

          gfx::AttributeBinding attrBinding;
          attrBinding.attribute.dataType = mapAttribuiteDataType(accessor.type);
          attrBinding.attribute.offset = accessor.byteOffset;
          attrBinding.vertexStride = bufferView.byteStride;
          attrBinding.vertexOffset = 0;
          attrBinding.vertexBufferResource = &gpuBufferView->vertexBuffer.value().getResource();

          int32_t index = getAttributeIndex(attrIt.first, material.pbrMetallicRoughness.baseColorTexture.texCoord);
          if (index != -1)
          {
              vecAttrBinding[index] = attrBinding;
          }
      }

    seg->attributeBindings = {vecAttrBinding[0], vecAttrBinding[2]};
    
    // 上传图片到GPU
    tinygltf::Parameter &param = material.values.find("baseColorTexture")->second;

    if (param.json_double_value.size() == 0) {
        tinygltf::Parameter &colorParam = material.values.find("baseColorFactor")->second;

        seg->bHasTexture = false;

        if (material.name == "materialLightPole") {
            seg->albedo = Point3D<float>(0.028426, 0.048172, 0.132868);
            seg->metallic = 0.285714;
            seg->roughness = 0.428571;
        } else if (material.name == "SilveryMetal") {
            seg->albedo = Point3D<float>(0.168269, 0.198069, 0.246201);
            seg->metallic = 0.571429;
            seg->roughness = 0.285714;
        } else if (material.name == "WhitePaint") {
            seg->albedo = Point3D<float>(0.991102, 0.991102, 0.991102);
            seg->metallic = 0.571429;
            seg->roughness = 0.285714;
        } else if (material.name == "RedPaint") {
            seg->albedo = Point3D<float>(0.665387, 0.009134, 0.008023);
            seg->metallic = 0.571429;
            seg->roughness = 0.285714;
        } else if (material.name == "BluePaint") {
            seg->albedo = Point3D<float>(0.006049, 0.061246, 0.520996);
            seg->metallic = 0.428571;
            seg->roughness = 0.285714;
        } else if (material.name == "BlackPaint") {
            seg->albedo = Point3D<float>(0.0, 0.0, 0.0);
            seg->metallic = 0.571429;
            seg->roughness = 0.285714;
        } else if (material.name == "CementBlock") {
            seg->albedo = Point3D<float>(0.095307, 0.093059, 0.095307);
            seg->metallic = 0.428571;
            seg->roughness = 0.714286;
        } else if (material.name == "BrassMetal") {
            seg->albedo = Point3D<float>(0.226966, 0.127438, 0.036889);
            seg->metallic = 0.142857;
            seg->roughness = 0.714286;
        } else if (material.name == "GreenMetal") {
            seg->albedo = Point3D<float>(0.03125, 0.05, 0.005);
            seg->metallic = 0.571429;
            seg->roughness = 0.285714;
        } else if (material.name == "ParkingGround") {
            seg->albedo = Point3D<float>(0.285871, 0.36, 0.0954);
            seg->metallic = 0.0;
            seg->roughness = 0.857143;
        }
        else if (material.name == "materialRoadSurface") {
            // 路面颜色值
//            seg->albedo = Point3D<float>(0.124772, 0.141263, 0.171441);
            seg->albedo = Point3D<float>(0.177888, 0.174647, 0.226966);//路面基础颜色
            seg->metallic = 0.428571;
            seg->roughness = 0.714286;
        } else if (material.name == "materialYellowLine") {
            seg->albedo = Point3D<float>(0.135633, 0.082283, 0.001214);
            seg->metallic = 0.571429;
            seg->roughness = 0.285714;
        } else if (material.name == "materialWhiteLine") {
            seg->albedo = Point3D<float>(0.758376, 0.758376, 0.758376);
            seg->metallic = 0.285714;
            seg->roughness = 0.428571;
        } else {
            if (colorParam.number_array.size() > 0)
            {
                seg->albedo = Point3D<float>(colorParam.number_array[0], colorParam.number_array[1], colorParam.number_array[2]);
            }
            else
            {
                seg->albedo = Point3D<float>(1.0, 0.0, 0.0);
            }
            seg->metallic = 0.142857;
            seg->roughness = 0.857143;
        }

        seg->metallic = 0.0;
        seg->roughness = 0.9;
    } else {
        int index = param.TextureIndex();
        const tinygltf::Image& image = model->images[index];
        if (image.bufferView != -1) {
            
            seg->mbTexture = mTextures[index]; // 可能多个primitive共用一个image

            seg->loadedTexture= true;

        } else if (!image.uri.empty()) {
            //从纹理池取
            seg->textureId = NormalizePath(image.uri);
            // TODO: 临时
//            seg->textureId = NormalizePath("funan/"+image.uri);
            seg->mExtTexture = mTextureManager->getTexture(seg->textureId);
            if (!seg->mExtTexture) {
                seg->mExtTexture = mTextureManager->mSpaceTexture;
                seg->loadedTexture= false;
            } else
            {
                seg->loadedTexture= true;
            }
        }
        seg->bHasTexture = true;
        seg->metallic = 1.0;//有材质时默认金属度和粗糙度
        seg->roughness = 1.0;
    }
}

2.render

void FX3dtilesRender::render(PaintParameters& parameters) {
    assert(render3dTiles);
    
    if (parameters.pass != RenderPass::Opaque) {
        return;
    }

    auto& context = static_cast<gl::Context&>(parameters.context);
    const auto& evaluated = static_cast<const FX3dtilesLayerProperties&>(*evaluatedProperties).evaluated;
    
    FX3dtilesProgram::Binders paintAttributeData { evaluated, 0 };
    const auto depthMode = parameters.depthModeFor3D();
    const TransformState& state = parameters.state;

    math::mat4 projMat;
    state.getProjectionMatrix(projMat);
    
    math::mat4 viewMat;
    state.getViewMatrix(viewMat);
    math::mat4 invViewMat;
    invViewMat *= viewMat;
    
    math::mat4 modelMat;
    state.getModelMatrix(modelMat);
    
    math::mat4 mercatorToMapboxMat;
    tilegltf::util::getMercatorToMapboxMat(mercatorToMapboxMat, state);

    if (!render3dTiles->empty()) {

        context.setDepthMode(depthMode);
        context.setStencilMode(gfx::StencilMode::disabled());
        context.setColorMode(parameters.colorModeForRenderPass());
        context.setCullFaceMode(gfx::CullFaceMode::backCCW());

        for (const Render3dTile& tile : *render3dTiles) {
            auto* bucket_ = tile.getBucket(*baseImpl);
            auto& bucket = static_cast<FX3dtilesBucket&>(*bucket_);
            bucket.preRender();
            
            auto& fx3dtile_ = tile.GetDataTile();
            auto& fx3dtile = static_cast<const FX3dtilesTile&>(fx3dtile_);
            
            math::mat4 tileModelMat;
            tileModelMat *= mercatorToMapboxMat;
            tileModelMat *= fx3dtile.getMatrix();
            tileModelMat = modelMat * tileModelMat;
            
            for (FX3dtilesSegment* seg : bucket.Segments) {
                
                assert(seg->indexBufferView < (int32_t)bucket._gpuBufferViews.size());
                auto& gpuBufferView = bucket._gpuBufferViews[seg->indexBufferView];
                assert(gpuBufferView->indexBuffer.has_value());
                
                drawTile(evaluated, paintAttributeData, parameters,
                         seg, gpuBufferView->indexBuffer.value(),
                         projMat, viewMat, tileModelMat);
            }
        }
    }
}

3.shader

const char* fx3dtiles_vs() { return R"(precision highp float;
attribute vec3 a_pos3f;
attribute vec2 a_texture2f;

uniform mat4 u_proj_mat;
uniform mat4 u_view_mat;
uniform mat4 u_model_mat;

varying vec2 v_tex_coord;

void main() {

    v_tex_coord = a_texture2f;

    vec4 loc_pos = u_model_mat * vec4(a_pos3f, 1.0);
    gl_Position =  u_proj_mat * u_view_mat * loc_pos;
}
)"; }

const char* fx3dtiles_fs() { return R"(precision highp float;
varying vec2 v_tex_coord;

uniform sampler2D u_color_map;
uniform float u_is_has_texture;

void main() {
    vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
    if (u_is_has_texture > -1.0) {
        color = texture2D(u_color_map, v_tex_coord);
    }
    else
    {
        color = vec4(1.0, 0.0, 0.0, 1.0);
    }
 
    gl_FragColor = color;
}
)"; }

相关文章

  • WordPress分页插件 – WP-PageNavi的使用(替换现有脚本)WordPress分页插件 – WP-PageNavi的使用(替换现有脚本)
  • ODA的基本操作-平移、旋转、矩阵变换ODA的基本操作-平移、旋转、矩阵变换
  • Filament加载并渲染glTF模型Filament加载并渲染glTF模型
  • 通过Spy++抓取窗口以查询对话框id通过Spy++抓取窗口以查询对话框id
  • Civil Designer开发-检测规范自动生成控制截面Civil Designer开发-检测规范自动生成控制截面
  • C++实现一个简单的语言解释器C++实现一个简单的语言解释器

GIS
3dtiles

Post navigation

PREVIOUS
视锥体与AABB和OBB包围盒相交判断
NEXT
Filament加载并渲染glTF模型

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

近期文章

  • IBL计算总结
  • Filament加载并渲染glTF模型
  • 在mapbox中实现3dtiles的加载和绘制(移动端)
  • 视锥体与AABB和OBB包围盒相交判断
  • 倾斜摄影在mapbox中的坐标转换
  • 解析3dTiles数据和提取b3dm模型文件
  • C++实现一个简单的语言解释器
  • OpenGL-法线贴图(Normal Mapping)
  • OpenGL-卡通着色(Cartoon)
  • OpenGL几何着色器实现贝塞尔曲线
  • WinDbg检查内存泄漏
  • OpenGL雾化效果实现-每像素雾化
  • OpenGL实现billboard效果(CPU)
  • 算法:寻找异常数字
  • OpenGL 几何着色器的应用
  • Midas XD-构件详图开发
  • Midas XD-选筋助手开发
  • Civil Designer开发-检测规范自动生成控制截面
  • Civil Designer开发-公路桥梁承载能力检算评定
  • Midas W-满堂支架快速建模助手开发

全站热点

  • C++编写的情人节小程序 (1,503)
  • Modern OpenGL绘制圆柱体 (703)
  • 提取最小封闭区域 (634)
  • 判断一个点是否在闭合区域内 (536)
  • 截面特性计算程序-附源码 (524)
  • OpenGL开发环境搭建-GLFW与GLAD配置 超详细 (487)
  • OpenGL绘制旋转立方体 (435)
  • Midas XD [错误] 右侧挡土墙的最下端深度必须小于地基的最下端深度 (411)
  • WordPress分页插件 – WP-PageNavi的使用(替换现有脚本) (375)
  • 使用ODA数据库出现 “ODA_ASSUME”: 找不到标识符的错误 (350)
  • 解析3dTiles数据和提取b3dm模型文件 (345)
  • 土木想往土木软件开发方向发展,应该如何准备 (330)
  • #pragma message 编译时提示信息 (322)
  • midas XD2020的开发 (317)
  • 从DLL中动态加载一个函数:LoadLibrary和GetProcAddress的使用 (308)
  • 两跨连续梁影响线绘制-附源码 (300)
  • Midas W-满堂支架快速建模助手开发 (290)
  • 算法:寻找异常数字 (249)
  • OpenGL几何着色器实现贝塞尔曲线 (174)
  • 通过Spy++抓取窗口以查询对话框id (173)

分类

  • C# (3)
  • C++ (17)
  • Filament (1)
  • GIS (3)
  • MFC (3)
  • ObjectARX (2)
  • OpenGL (12)
  • Revit开发 (1)
  • 岩土 (2)
  • 算法 (1)
  • 结构设计 (7)
  • 职场生涯 (1)
  • 计算几何 (3)

归档

  • 2022年10月 (3)
  • 2022年7月 (2)
  • 2022年4月 (1)
  • 2022年3月 (1)
  • 2022年2月 (1)
  • 2022年1月 (5)
  • 2021年11月 (7)
  • 2021年6月 (3)
  • 2021年5月 (2)
  • 2021年3月 (2)
  • 2021年2月 (8)
  • 2021年1月 (18)

标签

3dtiles b3dm Bezier Curves BillBoard C++ CDN CivilDesigner DLL EasyX filament fog GIS glTF json mapbox MFC Midas W Midas XD NormalMapping ObjectARX ODA OpenGL OpenXML Open XML PBR revit WinDbg 基坑设计 影响线 截面特性 桥梁 桥梁检测 桥梁设计 算法 计算几何 设计模式

书签

  • 李燕良的CSDN
  • 崔济东的博客
  • C++爱好者博客
  • 陈学伟的博客
  • 贾苏的博客
  • 陈睦锋的博客
  • 孙勇的博客

统计

  • 0
  • 21
  • 6
  • 57
  • 23
  • 69,967
  • 21,748

实时访问地域

© 2023   liyanliang.net Copyright. All Rights Reserved.