mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-13 10:47:23 +02:00
429 lines
16 KiB
C++
429 lines
16 KiB
C++
//
|
|
// MeshPartPayload.cpp
|
|
// interface/src/renderer
|
|
//
|
|
// Created by Sam Gateau on 10/3/15.
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include "MeshPartPayload.h"
|
|
|
|
#include <BillboardMode.h>
|
|
#include <PerfStat.h>
|
|
#include <DualQuaternion.h>
|
|
#include <graphics/ShaderConstants.h>
|
|
|
|
#include "render-utils/ShaderConstants.h"
|
|
#include <procedural/Procedural.h>
|
|
#include "DeferredLightingEffect.h"
|
|
|
|
#include "RenderPipelines.h"
|
|
|
|
using namespace render;
|
|
|
|
bool ModelMeshPartPayload::enableMaterialProceduralShaders = false;
|
|
|
|
ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex,
|
|
const Transform& transform, const uint64_t& created) :
|
|
_meshIndex(meshIndex),
|
|
_created(created) {
|
|
|
|
assert(model && model->isLoaded());
|
|
|
|
auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex);
|
|
_meshNumVertices = (int)modelMesh->getNumVertices();
|
|
const Model::MeshState& state = model->getMeshState(_meshIndex);
|
|
|
|
updateMeshPart(modelMesh, partIndex);
|
|
|
|
bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning();
|
|
if (useDualQuaternionSkinning) {
|
|
computeAdjustedLocalBound(state.clusterDualQuaternions);
|
|
} else {
|
|
computeAdjustedLocalBound(state.clusterMatrices);
|
|
}
|
|
|
|
updateTransformForSkinnedMesh(transform, state, useDualQuaternionSkinning);
|
|
|
|
initCache(model, shapeIndex);
|
|
|
|
#if defined(Q_OS_MAC) || defined(Q_OS_ANDROID)
|
|
// On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline
|
|
// it cannot be null otherwise we crash in the drawcall using a deformed pipeline with a skinned only (not blendshaped) mesh
|
|
if (_isBlendShaped) {
|
|
std::vector<BlendshapeOffset> data(_meshNumVertices);
|
|
const auto blendShapeBufferSize = _meshNumVertices * sizeof(BlendshapeOffset);
|
|
_meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(blendShapeBufferSize, reinterpret_cast<const gpu::Byte*>(data.data()), blendShapeBufferSize);
|
|
} else if (_isSkinned) {
|
|
BlendshapeOffset data;
|
|
_meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(sizeof(BlendshapeOffset), reinterpret_cast<const gpu::Byte*>(&data), sizeof(BlendshapeOffset));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ModelMeshPartPayload::initCache(const ModelPointer& model, int shapeID) {
|
|
if (_drawMesh) {
|
|
auto vertexFormat = _drawMesh->getVertexFormat();
|
|
_isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX);
|
|
|
|
const HFMModel& hfmModel = model->getHFMModel();
|
|
const HFMMesh& mesh = hfmModel.meshes.at(_meshIndex);
|
|
|
|
_isBlendShaped = !mesh.blendshapes.isEmpty();
|
|
_hasTangents = !mesh.tangents.isEmpty();
|
|
}
|
|
|
|
auto networkMaterial = model->getGeometry()->getShapeMaterial(shapeID);
|
|
if (networkMaterial) {
|
|
addMaterial(graphics::MaterialLayer(networkMaterial, 0));
|
|
}
|
|
}
|
|
|
|
void ModelMeshPartPayload::updateMeshPart(const std::shared_ptr<const graphics::Mesh>& drawMesh, int partIndex) {
|
|
_drawMesh = drawMesh;
|
|
if (_drawMesh) {
|
|
auto vertexFormat = _drawMesh->getVertexFormat();
|
|
_drawPart = _drawMesh->getPartBuffer().get<graphics::Mesh::Part>(partIndex);
|
|
_localBound = _drawMesh->evalPartBound(partIndex);
|
|
}
|
|
}
|
|
|
|
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices) {
|
|
// reset cluster buffer if we change the cluster buffer type
|
|
if (_clusterBufferType != ClusterBufferType::Matrices) {
|
|
_clusterBuffer.reset();
|
|
}
|
|
_clusterBufferType = ClusterBufferType::Matrices;
|
|
|
|
// Once computed the cluster matrices, update the buffer(s)
|
|
if (clusterMatrices.size() > 1) {
|
|
if (!_clusterBuffer) {
|
|
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterMatrices.size() * sizeof(glm::mat4),
|
|
(const gpu::Byte*) clusterMatrices.data());
|
|
} else {
|
|
_clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4),
|
|
(const gpu::Byte*) clusterMatrices.data());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions) {
|
|
// reset cluster buffer if we change the cluster buffer type
|
|
if (_clusterBufferType != ClusterBufferType::DualQuaternions) {
|
|
_clusterBuffer.reset();
|
|
}
|
|
_clusterBufferType = ClusterBufferType::DualQuaternions;
|
|
|
|
// Once computed the cluster matrices, update the buffer(s)
|
|
if (clusterDualQuaternions.size() > 1) {
|
|
if (!_clusterBuffer) {
|
|
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion),
|
|
(const gpu::Byte*) clusterDualQuaternions.data());
|
|
} else {
|
|
_clusterBuffer->setSubData(0, clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion),
|
|
(const gpu::Byte*) clusterDualQuaternions.data());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices) {
|
|
_adjustedLocalBound = _localBound;
|
|
if (clusterMatrices.size() > 0) {
|
|
_adjustedLocalBound.transform(clusterMatrices.back());
|
|
|
|
for (int i = 0; i < (int)clusterMatrices.size() - 1; ++i) {
|
|
AABox clusterBound = _localBound;
|
|
clusterBound.transform(clusterMatrices[i]);
|
|
_adjustedLocalBound += clusterBound;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<Model::TransformDualQuaternion>& clusterDualQuaternions) {
|
|
_adjustedLocalBound = _localBound;
|
|
if (clusterDualQuaternions.size() > 0) {
|
|
Transform rootTransform(clusterDualQuaternions.back().getRotation(),
|
|
clusterDualQuaternions.back().getScale(),
|
|
clusterDualQuaternions.back().getTranslation());
|
|
_adjustedLocalBound.transform(rootTransform);
|
|
|
|
for (int i = 0; i < (int)clusterDualQuaternions.size() - 1; ++i) {
|
|
AABox clusterBound = _localBound;
|
|
Transform transform(clusterDualQuaternions[i].getRotation(),
|
|
clusterDualQuaternions[i].getScale(),
|
|
clusterDualQuaternions[i].getTranslation());
|
|
clusterBound.transform(transform);
|
|
_adjustedLocalBound += clusterBound;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& modelTransform, const Model::MeshState& meshState, bool useDualQuaternionSkinning) {
|
|
_localTransform = Transform();
|
|
if (useDualQuaternionSkinning) {
|
|
if (meshState.clusterDualQuaternions.size() == 1 || meshState.clusterDualQuaternions.size() == 2) {
|
|
const auto& dq = meshState.clusterDualQuaternions[0];
|
|
_localTransform = Transform(dq.getRotation(),
|
|
dq.getScale(),
|
|
dq.getTranslation());
|
|
}
|
|
} else {
|
|
if (meshState.clusterMatrices.size() == 1 || meshState.clusterMatrices.size() == 2) {
|
|
_localTransform = Transform(meshState.clusterMatrices[0]);
|
|
}
|
|
}
|
|
|
|
_parentTransform = modelTransform;
|
|
}
|
|
|
|
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) {
|
|
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
|
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
|
if (_meshBlendshapeBuffer) {
|
|
batch.setResourceBuffer(0, _meshBlendshapeBuffer);
|
|
}
|
|
batch.setInputStream(0, _drawMesh->getVertexStream());
|
|
}
|
|
|
|
void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& transform, RenderArgs::RenderMode renderMode) const {
|
|
if (_clusterBuffer) {
|
|
batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer);
|
|
}
|
|
batch.setModelTransform(transform);
|
|
}
|
|
|
|
void ModelMeshPartPayload::drawCall(gpu::Batch& batch) const {
|
|
batch.drawIndexed(gpu::TRIANGLES, _drawPart._numIndices, _drawPart._startIndex);
|
|
}
|
|
|
|
void ModelMeshPartPayload::updateKey(const render::ItemKey& key) {
|
|
ItemKey::Builder builder(key);
|
|
builder.withTypeShape();
|
|
|
|
if (_isBlendShaped || _isSkinned) {
|
|
builder.withDeformed();
|
|
}
|
|
|
|
if (_drawMaterials.shouldUpdate()) {
|
|
RenderPipelines::updateMultiMaterial(_drawMaterials);
|
|
}
|
|
|
|
auto matKey = _drawMaterials.getMaterialKey();
|
|
if (matKey.isTranslucent()) {
|
|
builder.withTransparent();
|
|
}
|
|
|
|
if (_cullWithParent) {
|
|
builder.withSubMetaCulled();
|
|
}
|
|
|
|
_itemKey = builder.build();
|
|
}
|
|
|
|
void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, PrimitiveMode primitiveMode, bool useDualQuaternionSkinning) {
|
|
if (invalidateShapeKey) {
|
|
_shapeKey = ShapeKey::Builder::invalid();
|
|
return;
|
|
}
|
|
|
|
if (_drawMaterials.shouldUpdate()) {
|
|
RenderPipelines::updateMultiMaterial(_drawMaterials);
|
|
}
|
|
|
|
ShapeKey::Builder builder;
|
|
graphics::MaterialPointer material = _drawMaterials.empty() ? nullptr : _drawMaterials.top().material;
|
|
graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey();
|
|
|
|
bool isWireframe = primitiveMode == PrimitiveMode::LINES;
|
|
|
|
if (isWireframe) {
|
|
builder.withWireframe();
|
|
} else if (drawMaterialKey.isTranslucent()) {
|
|
builder.withTranslucent();
|
|
}
|
|
|
|
if (_isSkinned || (_isBlendShaped && _meshBlendshapeBuffer)) {
|
|
builder.withDeformed();
|
|
if (useDualQuaternionSkinning) {
|
|
builder.withDualQuatSkinned();
|
|
}
|
|
}
|
|
|
|
if (material && material->isProcedural() && material->isReady()) {
|
|
builder.withOwnPipeline();
|
|
} else {
|
|
bool hasTangents = drawMaterialKey.isNormalMap() && _hasTangents;
|
|
bool hasLightmap = drawMaterialKey.isLightMap();
|
|
bool isUnlit = drawMaterialKey.isUnlit();
|
|
|
|
if (isWireframe) {
|
|
hasTangents = hasLightmap = false;
|
|
}
|
|
|
|
builder.withMaterial();
|
|
|
|
if (hasTangents) {
|
|
builder.withTangents();
|
|
}
|
|
if (hasLightmap) {
|
|
builder.withLightMap();
|
|
}
|
|
if (isUnlit) {
|
|
builder.withUnlit();
|
|
}
|
|
if (material) {
|
|
builder.withCullFaceMode(material->getCullFaceMode());
|
|
}
|
|
}
|
|
|
|
_prevUseDualQuaternionSkinning = useDualQuaternionSkinning;
|
|
_shapeKey = builder.build();
|
|
}
|
|
|
|
ItemKey ModelMeshPartPayload::getKey() const {
|
|
return _itemKey;
|
|
}
|
|
|
|
Item::Bound ModelMeshPartPayload::getBound(RenderArgs* args) const {
|
|
graphics::MaterialPointer material = _drawMaterials.empty() ? nullptr : _drawMaterials.top().material;
|
|
if (material && material->isProcedural() && material->isReady()) {
|
|
auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(_drawMaterials.top().material);
|
|
if (procedural->hasVertexShader() && procedural->hasBoundOperator()) {
|
|
return procedural->getBound(args);
|
|
}
|
|
}
|
|
|
|
auto worldBound = _adjustedLocalBound;
|
|
auto parentTransform = _parentTransform;
|
|
if (args) {
|
|
parentTransform.setRotation(BillboardModeHelpers::getBillboardRotation(parentTransform.getTranslation(), parentTransform.getRotation(), _billboardMode,
|
|
args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
|
|
}
|
|
worldBound.transform(parentTransform);
|
|
return worldBound;
|
|
}
|
|
|
|
ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
|
return _shapeKey;
|
|
}
|
|
|
|
void ModelMeshPartPayload::render(RenderArgs* args) {
|
|
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
|
|
|
|
if (!args || (args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE && _cauterized)) {
|
|
return;
|
|
}
|
|
|
|
gpu::Batch& batch = *(args->_batch);
|
|
|
|
Transform transform = _parentTransform;
|
|
transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
|
|
args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
|
|
|
|
Transform modelTransform = transform.worldTransform(_localTransform);
|
|
bindTransform(batch, modelTransform, args->_renderMode);
|
|
|
|
//Bind the index buffer and vertex buffer and Blend shapes if needed
|
|
bindMesh(batch);
|
|
|
|
// IF deformed pass the mesh key
|
|
auto drawcallInfo = (uint16_t) (((_isBlendShaped && _meshBlendshapeBuffer && args->_enableBlendshape) << 0) | ((_isSkinned && args->_enableSkinning) << 1));
|
|
if (drawcallInfo) {
|
|
batch.setDrawcallUniform(drawcallInfo);
|
|
}
|
|
|
|
if (_shapeKey.hasOwnPipeline()) {
|
|
if (!(enableMaterialProceduralShaders)) {
|
|
return;
|
|
}
|
|
auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(_drawMaterials.top().material);
|
|
auto& schema = _drawMaterials.getSchemaBuffer().get<graphics::MultiMaterial::Schema>();
|
|
glm::vec4 outColor = glm::vec4(ColorUtils::tosRGBVec3(schema._albedo), schema._opacity);
|
|
outColor = procedural->getColor(outColor);
|
|
procedural->prepare(batch, transform.getTranslation(), transform.getScale(), transform.getRotation(), _created,
|
|
ProceduralProgramKey(outColor.a < 1.0f, _shapeKey.isDeformed(), _shapeKey.isDualQuatSkinned()));
|
|
batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a);
|
|
} else {
|
|
// apply material properties
|
|
if (RenderPipelines::bindMaterials(_drawMaterials, batch, args->_renderMode, args->_enableTexturing)) {
|
|
args->_details._materialSwitches++;
|
|
}
|
|
}
|
|
|
|
// Draw!
|
|
{
|
|
PerformanceTimer perfTimer("batch.drawIndexed()");
|
|
drawCall(batch);
|
|
}
|
|
|
|
const int INDICES_PER_TRIANGLE = 3;
|
|
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
|
|
}
|
|
|
|
bool ModelMeshPartPayload::passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const {
|
|
if (!_renderWithZones.isEmpty()) {
|
|
if (!containingZones.empty()) {
|
|
for (auto renderWithZone : _renderWithZones) {
|
|
if (containingZones.find(renderWithZone) != containingZones.end()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ModelMeshPartPayload::setBlendshapeBuffer(const std::unordered_map<int, gpu::BufferPointer>& blendshapeBuffers, const QVector<int>& blendedMeshSizes) {
|
|
if (_meshIndex < blendedMeshSizes.length() && blendedMeshSizes.at(_meshIndex) == _meshNumVertices) {
|
|
auto blendshapeBuffer = blendshapeBuffers.find(_meshIndex);
|
|
if (blendshapeBuffer != blendshapeBuffers.end()) {
|
|
_meshBlendshapeBuffer = blendshapeBuffer->second;
|
|
if (_isSkinned || (_isBlendShaped && _meshBlendshapeBuffer)) {
|
|
ShapeKey::Builder builder(_shapeKey);
|
|
builder.withDeformed();
|
|
if (_prevUseDualQuaternionSkinning) {
|
|
builder.withDualQuatSkinned();
|
|
}
|
|
_shapeKey = builder.build();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace render {
|
|
template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload) {
|
|
if (payload) {
|
|
return payload->getKey();
|
|
}
|
|
return ItemKey::Builder::opaqueShape(); // for lack of a better idea
|
|
}
|
|
|
|
template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args) {
|
|
if (payload) {
|
|
return payload->getBound(args);
|
|
}
|
|
return Item::Bound();
|
|
}
|
|
|
|
template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload) {
|
|
if (payload) {
|
|
return payload->getShapeKey();
|
|
}
|
|
return ShapeKey::Builder::invalid();
|
|
}
|
|
|
|
template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args) {
|
|
return payload->render(args);
|
|
}
|
|
|
|
template <> bool payloadPassesZoneOcclusionTest(const ModelMeshPartPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones) {
|
|
if (payload) {
|
|
return payload->passesZoneOcclusionTest(containingZones);
|
|
}
|
|
return false;
|
|
}
|
|
}
|