From 4c0dc02bbaa35f52450c5742eb9dbddeeaeee706 Mon Sep 17 00:00:00 2001 From: Benito Mota Date: Mon, 9 Nov 2026 08:36:33 -0600 Subject: [PATCH 1/5] PBR: ambient by metalness, normal scale, occlusion strength, occlusion on a second UV set --- ViroRenderer/VROGeometrySource.h | 4 ++ ViroRenderer/VROGeometryUtil.cpp | 4 ++ ViroRenderer/VROMaterialShaderBinding.cpp | 10 ++++ ViroRenderer/VROMaterialShaderBinding.h | 2 + ViroRenderer/VROMaterialVisual.cpp | 11 +++- ViroRenderer/VROMaterialVisual.h | 17 +++++- ViroRenderer/VROShaderCapabilities.cpp | 2 + ViroRenderer/VROShaderCapabilities.h | 5 +- ViroRenderer/VROShaderFactory.cpp | 63 ++++++++++++++++++----- ViroRenderer/VROShaderFactory.h | 3 +- ViroRenderer/VROShaderProgram.cpp | 3 ++ ViroRenderer/VROShaderProgram.h | 1 + 12 files changed, 107 insertions(+), 18 deletions(-) diff --git a/ViroRenderer/VROGeometrySource.h b/ViroRenderer/VROGeometrySource.h index 8b1378b21..862ca4c95 100644 --- a/ViroRenderer/VROGeometrySource.h +++ b/ViroRenderer/VROGeometrySource.h @@ -51,6 +51,10 @@ enum class VROGeometrySourceSemantic { Morph_4, Morph_5, Morph_6, + // A second set of texture coordinates, for a texture that samples its own UV set + // (glTF occlusion on TEXCOORD_1, a lightmap). Bound at the EdgeCrease slot, which + // no shader reads. + Texcoord1, Invalid }; diff --git a/ViroRenderer/VROGeometryUtil.cpp b/ViroRenderer/VROGeometryUtil.cpp index d0827efda..78bd34450 100644 --- a/ViroRenderer/VROGeometryUtil.cpp +++ b/ViroRenderer/VROGeometryUtil.cpp @@ -179,6 +179,10 @@ int VROGeometryUtilParseAttributeIndex(VROGeometrySourceSemantic semantic) { return 14; case VROGeometrySourceSemantic::Morph_6: return 15; + case VROGeometrySourceSemantic::Texcoord1: + // GL ES 3 guarantees 16 attributes and 0-15 are taken; EdgeCrease (6) comes + // only from VRX subdivision data and no shader reads it. + return 6; default: return 0; } diff --git a/ViroRenderer/VROMaterialShaderBinding.cpp b/ViroRenderer/VROMaterialShaderBinding.cpp index 72dc77ac2..0f2121226 100644 --- a/ViroRenderer/VROMaterialShaderBinding.cpp +++ b/ViroRenderer/VROMaterialShaderBinding.cpp @@ -57,6 +57,8 @@ VROMaterialShaderBinding::VROMaterialShaderBinding(std::shared_ptrgetUniform("material_emissive_color"); _textureTransformUniform = program->getUniform("material_texture_transform"); _emissiveTextureTransformUniform = program->getUniform("material_emissive_texture_transform"); + _normalScaleUniform = program->getUniform("material_normal_scale"); + _aoStrengthUniform = program->getUniform("material_ao_strength"); _normalMatrixUniform = program->getUniform("normal_matrix"); _modelMatrixUniform = program->getUniform("model_matrix"); @@ -317,6 +321,12 @@ void VROMaterialShaderBinding::bindMaterialUniforms(const VROMaterial &material, if (_textureTransformUniform != nullptr) { _textureTransformUniform->setMat4(material.getDiffuse().getContentsTransform()); } + if (_normalScaleUniform != nullptr) { + _normalScaleUniform->setFloat(material.getNormal().getIntensity()); + } + if (_aoStrengthUniform != nullptr) { + _aoStrengthUniform->setFloat(material.getAmbientOcclusion().getIntensity()); + } if (_emissiveTextureTransformUniform != nullptr) { _emissiveTextureTransformUniform->setMat4(material.getEmission().getContentsTransform()); } diff --git a/ViroRenderer/VROMaterialShaderBinding.h b/ViroRenderer/VROMaterialShaderBinding.h index ba1aee724..d6c387bd1 100644 --- a/ViroRenderer/VROMaterialShaderBinding.h +++ b/ViroRenderer/VROMaterialShaderBinding.h @@ -116,6 +116,8 @@ class VROMaterialShaderBinding { VROUniform *_emissiveColorUniform; VROUniform *_textureTransformUniform; VROUniform *_emissiveTextureTransformUniform; + VROUniform *_normalScaleUniform; + VROUniform *_aoStrengthUniform; VROUniform *_normalMatrixUniform; VROUniform *_modelMatrixUniform; diff --git a/ViroRenderer/VROMaterialVisual.cpp b/ViroRenderer/VROMaterialVisual.cpp index 42d17120a..bae6fa5f7 100644 --- a/ViroRenderer/VROMaterialVisual.cpp +++ b/ViroRenderer/VROMaterialVisual.cpp @@ -36,7 +36,8 @@ VROMaterialVisual::VROMaterialVisual(VROMaterial &material, const VROMaterialVis _contentsTexture(visual._contentsTexture), _intensity(visual._intensity), _contentsTransform(visual._contentsTransform), - _hasContentsTransform(visual._hasContentsTransform) + _hasContentsTransform(visual._hasContentsTransform), + _texcoordSet(visual._texcoordSet) {} void VROMaterialVisual::deleteGL() { @@ -52,6 +53,14 @@ void VROMaterialVisual::copyFrom(const VROMaterialVisual &visual) { _intensity = visual._intensity; _contentsTransform = visual._contentsTransform; _hasContentsTransform = visual._hasContentsTransform; + _texcoordSet = visual._texcoordSet; +} + +void VROMaterialVisual::setTexcoordSet(int set) { + if (_texcoordSet != set) { + _texcoordSet = set; + _material.updateSubstrate(); + } } void VROMaterialVisual::setContentsTransform(VROMatrix4f transform) { diff --git a/ViroRenderer/VROMaterialVisual.h b/ViroRenderer/VROMaterialVisual.h index 572b5b35c..097cc3691 100644 --- a/ViroRenderer/VROMaterialVisual.h +++ b/ViroRenderer/VROMaterialVisual.h @@ -45,7 +45,8 @@ class VROMaterialVisual { _permissibleContentsMask(permissibleContentsMask), _contentsColor({ 1.0, 1.0, 1.0, 1.0 }), _intensity(1.0), - _hasContentsTransform(false) + _hasContentsTransform(false), + _texcoordSet(0) {} VROMaterialVisual(VROMaterial &material, int permissibleContentsMask, VROVector4f color) : @@ -53,7 +54,8 @@ class VROMaterialVisual { _permissibleContentsMask(permissibleContentsMask), _contentsColor(color), _intensity(1.0), - _hasContentsTransform(false) + _hasContentsTransform(false), + _texcoordSet(0) {} /* @@ -122,6 +124,16 @@ class VROMaterialVisual { bool hasContentsTransform() const { return _hasContentsTransform; } + + /* + The set of texture coordinates this visual's texture samples: 0 (the geometry's + texcoord source) or 1 (its Texcoord1 source). Only the ambient occlusion visual + reads a second set (a glTF occlusion map or lightmap on TEXCOORD_1). + */ + void setTexcoordSet(int set); + int getTexcoordSet() const { + return _texcoordSet; + } std::shared_ptr getTexture() const { if (_contentsTexture) { @@ -171,6 +183,7 @@ class VROMaterialVisual { */ VROMatrix4f _contentsTransform; bool _hasContentsTransform; + int _texcoordSet; }; diff --git a/ViroRenderer/VROShaderCapabilities.cpp b/ViroRenderer/VROShaderCapabilities.cpp index 3e06cbb92..61b08442b 100644 --- a/ViroRenderer/VROShaderCapabilities.cpp +++ b/ViroRenderer/VROShaderCapabilities.cpp @@ -46,6 +46,7 @@ VROMaterialShaderCapabilities VROShaderCapabilities::deriveMaterialCapabilitiesK cap.textureTransform = false; cap.emissiveTexture = false; cap.ignoresDiffuseAlpha = material.getIgnoresDiffuseAlpha(); + cap.aoTexcoord1 = false; cap.diffuseTextureStereoMode = VROStereoMode::None; cap.bloom = false; cap.postProcessMask = false; @@ -123,6 +124,7 @@ VROMaterialShaderCapabilities VROShaderCapabilities::deriveMaterialCapabilitiesK } if (ao.getTextureType() == VROTextureType::Texture2D) { cap.aoMap = true; + cap.aoTexcoord1 = ao.getTexcoordSet() == 1; } // Lighting Model. If we don't have a specular texture then we fall back to Lambert diff --git a/ViroRenderer/VROShaderCapabilities.h b/ViroRenderer/VROShaderCapabilities.h index 7ab2f4207..cb25ce6b6 100644 --- a/ViroRenderer/VROShaderCapabilities.h +++ b/ViroRenderer/VROShaderCapabilities.h @@ -65,6 +65,7 @@ struct VROMaterialShaderCapabilities { bool textureTransform; bool emissiveTexture; bool ignoresDiffuseAlpha; + bool aoTexcoord1; bool bloom; bool postProcessMask; bool receivesShadows; @@ -75,13 +76,13 @@ struct VROMaterialShaderCapabilities { bool operator< (const VROMaterialShaderCapabilities& r) const { return std::tie(lightingModel, diffuseTexture, diffuseTextureStereoMode, diffuseEGLModifier, specularTexture, normalTexture, reflectiveTexture, - roughnessMap, metalnessMap, aoMap, textureTransform, emissiveTexture, ignoresDiffuseAlpha, bloom, postProcessMask, + roughnessMap, metalnessMap, aoMap, textureTransform, emissiveTexture, ignoresDiffuseAlpha, aoTexcoord1, bloom, postProcessMask, receivesShadows, chromaKeyFiltering, chromaKeyRed, chromaKeyGreen, chromaKeyBlue, additionalModifierKeys) < std::tie(r.lightingModel, r.diffuseTexture, r.diffuseTextureStereoMode, r.diffuseEGLModifier, r.specularTexture, r.normalTexture, r.reflectiveTexture, - r.roughnessMap, r.metalnessMap, r.aoMap, r.textureTransform, r.emissiveTexture, r.ignoresDiffuseAlpha, r.bloom, r.postProcessMask, + r.roughnessMap, r.metalnessMap, r.aoMap, r.textureTransform, r.emissiveTexture, r.ignoresDiffuseAlpha, r.aoTexcoord1, r.bloom, r.postProcessMask, r.receivesShadows, r.chromaKeyFiltering, r.chromaKeyRed, r.chromaKeyGreen, r.chromaKeyBlue, r.additionalModifierKeys); diff --git a/ViroRenderer/VROShaderFactory.cpp b/ViroRenderer/VROShaderFactory.cpp index 9fd1c8804..5ab55c85f 100644 --- a/ViroRenderer/VROShaderFactory.cpp +++ b/ViroRenderer/VROShaderFactory.cpp @@ -45,6 +45,8 @@ static thread_local std::shared_ptr sDiffuseTextureModifier; static thread_local std::shared_ptr sSpecularTextureModifier; static thread_local std::shared_ptr sEmissiveTextureModifier; static thread_local std::shared_ptr sOpaqueAlphaModifier; +static thread_local std::shared_ptr sAOTexcoord1TextureModifier; +static thread_local std::shared_ptr sTexcoord1GeometryModifier; static thread_local std::shared_ptr sNormalMapTextureModifier; static thread_local std::shared_ptr sReflectiveTextureModifier; static thread_local std::shared_ptr sRoughnessTextureModifier; @@ -228,7 +230,10 @@ std::shared_ptr VROShaderFactory::buildShader(VROShaderCapabil } if (materialCapabilities.aoMap) { samplers.push_back("ao_map"); - modifiers.push_back(createAOTextureModifier()); + if (materialCapabilities.aoTexcoord1) { + modifiers.push_back(createTexcoord1GeometryModifier()); + } + modifiers.push_back(createAOTextureModifier(materialCapabilities.aoTexcoord1)); } modifiers.push_back(createPBRSurfaceModifier()); modifiers.push_back(createPBRDirectLightingModifier()); @@ -493,7 +498,11 @@ std::shared_ptr VROShaderFactory::createNormalMapTextureModif // result after multiplying by TBN. If we remove one, the 'glinting' effect in PBR disappears as // we lose unit length "uniform sampler2D normal_texture;", - "_surface.normal = normalize(v_tbn * normalize( texture(normal_texture, _surface.diffuse_texcoord).xyz * 2.0 - 1.0 ));" + "uniform highp float material_normal_scale;", + // glTF normalTexture.scale: scales the sampled X and Y before normalizing + "highp vec3 normal_sample = texture(normal_texture, _surface.diffuse_texcoord).xyz * 2.0 - 1.0;", + "normal_sample.xy *= material_normal_scale;", + "_surface.normal = normalize(v_tbn * normalize(normal_sample));" }; sNormalMapTextureModifier = std::make_shared(VROShaderEntryPoint::Surface, modifierCode); @@ -529,17 +538,46 @@ std::shared_ptr VROShaderFactory::createMetalnessTextureModif return sMetalnessTextureModifier; } -std::shared_ptr VROShaderFactory::createAOTextureModifier() { - if (!sAOTextureModifier) { +std::shared_ptr VROShaderFactory::createAOTextureModifier(bool secondTexcoordSet) { + /* + Modifier that reads ambient occlusion from the red channel of the AO map, scaled toward + 1 by the occlusion strength (glTF occlusionTexture.strength). With secondTexcoordSet the + map samples the geometry's Texcoord1 (a lightmap on its own UV set), untransformed. + */ + std::shared_ptr &modifier = secondTexcoordSet ? sAOTexcoord1TextureModifier : sAOTextureModifier; + if (!modifier) { std::vector modifierCode = { "uniform sampler2D ao_map;", - "_surface.ao = texture(ao_map, _surface.diffuse_texcoord).r;" + "uniform highp float material_ao_strength;", + secondTexcoordSet + ? "_surface.ao = mix(1.0, texture(ao_map, v_texcoord1).r, material_ao_strength);" + : "_surface.ao = mix(1.0, texture(ao_map, _surface.diffuse_texcoord).r, material_ao_strength);" }; - sAOTextureModifier = std::make_shared(VROShaderEntryPoint::Surface, - modifierCode); - sAOTextureModifier->setName("diffuse"); + modifier = std::make_shared(VROShaderEntryPoint::Surface, modifierCode); + modifier->setName(secondTexcoordSet ? "ao_uv1" : "ao"); + if (secondTexcoordSet) { + modifier->setVaryings({ "highp vec2 v_texcoord1" }); + } + } + return modifier; +} + +std::shared_ptr VROShaderFactory::createTexcoord1GeometryModifier() { + /* + Modifier that passes the geometry's second set of texture coordinates (Texcoord1) to the + fragment stage, for a texture that samples its own UV set. + */ + if (!sTexcoord1GeometryModifier) { + std::vector modifierCode = { + "in highp vec2 texcoord1;", + "v_texcoord1 = texcoord1;" + }; + sTexcoord1GeometryModifier = std::make_shared(VROShaderEntryPoint::Geometry, modifierCode); + sTexcoord1GeometryModifier->setName("uv1"); + sTexcoord1GeometryModifier->setAttributes((int) VROShaderMask::Tex1); + sTexcoord1GeometryModifier->setVaryings({ "highp vec2 v_texcoord1" }); } - return sAOTextureModifier; + return sTexcoord1GeometryModifier; } std::shared_ptr VROShaderFactory::createReflectiveTextureModifier() { @@ -824,13 +862,14 @@ std::shared_ptr VROShaderFactory::createPBRConstantAmbientFra std::vector modifierCode = { // In this shader there is no irradiance, we only have constant ambient light, // represented by _ambient. The effect of this light on a PBR surface is determined - // by just multiplying it by by the surface color (albedo) and the surface ambient - // occlusion texture (_surface.ao). + // by multiplying it by the surface color (albedo), the share of light a surface + // diffuses (1 - metalness: a metal has no diffuse response, and without an + // environment map there is nothing for it to reflect) and the ambient occlusion. // Because the _ambient term is used by AR shadows to compute how much to // diminish a shadow (in VROARShadow), we have to set this term to the final // ambient contribution. - "_ambient = _ambient * albedo * _surface.ao;", + "_ambient = _ambient * albedo * (1.0 - _surface.metalness) * _surface.ao;", "_output_color = vec4(_ambient + _diffuse + _emissive, _output_color.a);", }; sPBRConstantAmbientFragmentModifier = std::make_shared(VROShaderEntryPoint::Fragment, diff --git a/ViroRenderer/VROShaderFactory.h b/ViroRenderer/VROShaderFactory.h index fc3cd8f79..58a224a14 100644 --- a/ViroRenderer/VROShaderFactory.h +++ b/ViroRenderer/VROShaderFactory.h @@ -123,7 +123,8 @@ class VROShaderFactory { std::shared_ptr createReflectiveTextureModifier(); std::shared_ptr createRoughnessTextureModifier(); std::shared_ptr createMetalnessTextureModifier(); - std::shared_ptr createAOTextureModifier(); + std::shared_ptr createAOTextureModifier(bool secondTexcoordSet); + std::shared_ptr createTexcoord1GeometryModifier(); std::shared_ptr createLambertLightingModifier(); std::shared_ptr createPhongLightingModifier(); diff --git a/ViroRenderer/VROShaderProgram.cpp b/ViroRenderer/VROShaderProgram.cpp index 93ee751fa..d44bbd58f 100644 --- a/ViroRenderer/VROShaderProgram.cpp +++ b/ViroRenderer/VROShaderProgram.cpp @@ -575,6 +575,9 @@ void VROShaderProgram::bindAttributes() { if ((_attributes & (int)VROShaderMask::Tex) != 0) { GL( glBindAttribLocation(_program, VROGeometryUtilParseAttributeIndex(VROGeometrySourceSemantic::Texcoord), "texcoord") ); } + if ((_attributes & (int)VROShaderMask::Tex1) != 0) { + GL( glBindAttribLocation(_program, VROGeometryUtilParseAttributeIndex(VROGeometrySourceSemantic::Texcoord1), "texcoord1") ); + } if ((_attributes & (int)VROShaderMask::Color) != 0) { GL( glBindAttribLocation(_program, VROGeometryUtilParseAttributeIndex(VROGeometrySourceSemantic::Color), "color") ); } diff --git a/ViroRenderer/VROShaderProgram.h b/ViroRenderer/VROShaderProgram.h index 9d2a5644b..56c980855 100644 --- a/ViroRenderer/VROShaderProgram.h +++ b/ViroRenderer/VROShaderProgram.h @@ -67,6 +67,7 @@ enum class VROShaderMask { Morph_4 = 1024, Morph_5 = 2048, Morph_6 = 4096, + Tex1 = 8192, }; class VROShaderModifier; From 852997af43e8f2adf65579d4b3085f999957174d Mon Sep 17 00:00:00 2001 From: Ernesto Mendoza Date: Mon, 9 Nov 2026 08:36:33 -0600 Subject: [PATCH 2/5] glTF: occlusion on its own UV set, mirrored nodes wind the other way --- ViroRenderer/VROGLTFLoader.cpp | 108 ++++++++++++++++++++++++++++++--- ViroRenderer/VROGLTFLoader.h | 3 +- 2 files changed, 101 insertions(+), 10 deletions(-) diff --git a/ViroRenderer/VROGLTFLoader.cpp b/ViroRenderer/VROGLTFLoader.cpp index 36e4cdfd4..0e0de02d4 100644 --- a/ViroRenderer/VROGLTFLoader.cpp +++ b/ViroRenderer/VROGLTFLoader.cpp @@ -194,6 +194,23 @@ const tinygltf::Parameter *primaryTextureInfo(const tinygltf::Material &gMat) { return nullptr; } +/* + The UV set a material's occlusion map samples, when it is not the set its other + textures sample (a lightmap on TEXCOORD_1); -1 otherwise. + */ +int occlusionOwnTexcoordSet(const tinygltf::Model &gModel, int materialIndex, int primarySet) { + if (materialIndex < 0 || materialIndex >= (int) gModel.materials.size()) { + return -1; + } + const tinygltf::Material &gMat = gModel.materials[materialIndex]; + auto occlusion = gMat.additionalValues.find("occlusionTexture"); + if (occlusion == gMat.additionalValues.end() || occlusion->second.TextureIndex() < 0) { + return -1; + } + int set = readTextureTransform(occlusion->second).texCoord; + return set == primarySet ? -1 : set; +} + GLTFTextureTransform materialTextureTransform(const tinygltf::Model &gModel, int materialIndex) { if (materialIndex < 0 || materialIndex >= (int) gModel.materials.size()) { return GLTFTextureTransform(); @@ -578,6 +595,51 @@ std::map> sPointerTracks; std::set sAnimatedTransformMaterials; std::map> sNodesByIndex; std::map>> sMaterialsByIndex; +// glTF nodes whose world transform mirrors (negative determinant): their triangles wind +// the other way, so their indices are reversed when built. +std::set sMirroredNodes; +bool sBuildingMirroredMesh = false; + +// Collect the mirrored nodes of a scene: the sign of a world transform's determinant is +// the product of the signs of its nodes' local determinants. +void collectMirroredNodes(const tinygltf::Model &gModel, int nodeIndex, bool parentMirrored) { + if (nodeIndex < 0 || nodeIndex >= (int) gModel.nodes.size()) { + return; + } + const tinygltf::Node &n = gModel.nodes[nodeIndex]; + bool mirrored = parentMirrored; + if (n.matrix.size() == 16) { + const std::vector &m = n.matrix; + double det = m[0] * (m[5] * m[10] - m[9] * m[6]) - m[4] * (m[1] * m[10] - m[9] * m[2]) + m[8] * (m[1] * m[6] - m[5] * m[2]); + mirrored = mirrored != (det < 0); + } else if (n.scale.size() >= 3) { + mirrored = mirrored != (n.scale[0] * n.scale[1] * n.scale[2] < 0); + } + if (mirrored) { + sMirroredNodes.insert(nodeIndex); + } + for (int child : n.children) { + collectMirroredNodes(gModel, child, mirrored); + } +} + +// Reverse the winding of a triangle element (swap the 2nd and 3rd index of each). +void reverseTriangleWinding(std::shared_ptr &element) { + if (element->getPrimitiveType() != VROGeometryPrimitiveType::Triangle || !element->getData()) { + return; + } + int bytes = element->getBytesPerIndex(); + int count = element->getPrimitiveCount() * 3; + std::shared_ptr data = element->getData(); + if (count <= 0 || (bytes != 2 && bytes != 4) || data->getDataLength() < count * bytes) { + return; + } + std::vector copy((unsigned char *) data->getData(), (unsigned char *) data->getData() + count * bytes); + for (int t = 0; t < count; t += 3) { + std::swap_ranges(copy.begin() + (t + 1) * bytes, copy.begin() + (t + 2) * bytes, copy.begin() + (t + 2) * bytes); + } + element->setData(std::make_shared((void *) copy.data(), (int) copy.size(), VRODataOwnership::Copy)); +} } // namespace @@ -1187,6 +1249,8 @@ void VROGLTFLoader::clearCachedData() { sAnimatedTransformMaterials.clear(); sNodesByIndex.clear(); sMaterialsByIndex.clear(); + sMirroredNodes.clear(); + sBuildingMirroredMesh = false; } void VROGLTFLoader::injectBindPoseAnimations() { @@ -2328,6 +2392,9 @@ bool VROGLTFLoader::processScene(const tinygltf::Model &gModel, std::shared_ptr< sceneNode->setName(gScene.name); std::vector gNodeIndexes = gScene.nodes; + for (int gNodeIndex : gNodeIndexes) { + collectMirroredNodes(gModel, gNodeIndex, false); + } for (int gNodeIndex : gNodeIndexes) { // Fail fast if we have failed to process a node in the scene. if (!processNode(gModel, sceneNode, gNodeIndex, driver)) { @@ -2394,7 +2461,10 @@ bool VROGLTFLoader::processNode(const tinygltf::Model &gModel, std::shared_ptr= 0 && !processMesh(gModel, node, gModel.meshes[meshIndex], meshIndex, driver)) { + sBuildingMirroredMesh = gNode.skin < 0 && sMirroredNodes.count(gNodeIndex) > 0; + bool meshBuilt = meshIndex < 0 || processMesh(gModel, node, gModel.meshes[meshIndex], meshIndex, driver); + sBuildingMirroredMesh = false; + if (!meshBuilt) { return false; } @@ -2704,8 +2774,17 @@ bool VROGLTFLoader::processMesh(const tinygltf::Model &gModel, std::shared_ptr= 0 && + gPrimitive.attributes.find("TEXCOORD_" + VROStringUtil::toString(occlusionSet)) == gPrimitive.attributes.end()) { + occlusionSet = -1; + } bool successAttributes = processVertexAttributes(gModel, gPrimitive.attributes, sources, elements.size() - 1, driver, - textureTransform.texCoord); + textureTransform.texCoord, occlusionSet); + if (successVertex && sBuildingMirroredMesh) { + reverseTriangleWinding(elements.back()); + } processTangent(elements, sources, elements.size() - 1); if (!successVertex || !successAttributes) { @@ -2722,6 +2801,9 @@ bool VROGLTFLoader::processMesh(const tinygltf::Model &gModel, std::shared_ptrgetDiffuse().setContentsTransform(textureTransformMatrix(textureTransform)); } + if (occlusionSet >= 0) { + material->getAmbientOcclusion().setTexcoordSet(1); + } sMaterialsByIndex[gMatIndex].push_back(material); } @@ -3207,7 +3289,8 @@ bool VROGLTFLoader::processVertexAttributes(const tinygltf::Model &gModel, std::vector> &sources, size_t geoElementIndex, std::shared_ptr driver, - int texcoordSet) { + int texcoordSet, + int texcoord1Set) { if (gAttributes.size() <= 0) { return false; } @@ -3216,6 +3299,8 @@ bool VROGLTFLoader::processVertexAttributes(const tinygltf::Model &gModel, // primitive has it, that set is the texture coordinates, in place of TEXCOORD_0. std::string texcoordName = "TEXCOORD_" + VROStringUtil::toString(texcoordSet); bool otherTexcoordSet = texcoordSet > 0 && gAttributes.find(texcoordName) != gAttributes.end(); + // A second set, sampled by a texture with its own UV set (the occlusion map). + std::string texcoord1Name = texcoord1Set >= 0 ? "TEXCOORD_" + VROStringUtil::toString(texcoord1Set) : ""; // Iterate through each Vertex Attribute (Position / Normals / etc) to parse its data for (auto const& gAttribute : gAttributes) { @@ -3224,12 +3309,17 @@ bool VROGLTFLoader::processVertexAttributes(const tinygltf::Model &gModel, // Ensure we only parse valid attributes, gracefully skip over the ones we do not yet support VROGeometrySourceSemantic attributeType = getGeometryAttribute(attributeName); - if (otherTexcoordSet) { - if (attributeName == "TEXCOORD_0") { - continue; - } - if (attributeName == texcoordName) { - attributeType = VROGeometrySourceSemantic::Texcoord; + if (otherTexcoordSet && attributeName == "TEXCOORD_0") { + attributeType = VROGeometrySourceSemantic::Invalid; + } + if (otherTexcoordSet && attributeName == texcoordName) { + attributeType = VROGeometrySourceSemantic::Texcoord; + } + if (!texcoord1Name.empty() && attributeName == texcoord1Name) { + if (attributeName == texcoordName && attributeType == VROGeometrySourceSemantic::Texcoord) { + // Both sets are this one: it is read once, as the texcoord source. + } else { + attributeType = VROGeometrySourceSemantic::Texcoord1; } } if (attributeType == VROGeometrySourceSemantic::Invalid) { diff --git a/ViroRenderer/VROGLTFLoader.h b/ViroRenderer/VROGLTFLoader.h index b1c201a8a..73ecf86a5 100644 --- a/ViroRenderer/VROGLTFLoader.h +++ b/ViroRenderer/VROGLTFLoader.h @@ -130,7 +130,8 @@ class VROGLTFLoader { std::vector> &sources, size_t geoElementIndex, std::shared_ptr driver, - int texcoordSet = 0); + int texcoordSet = 0, + int texcoord1Set = -1); static void processTangent(std::vector> &elements, std::vector> &sources, size_t geoElementIndex); static void regenerateTangent(std::vector &posArray, From cc4d4ab321534b88a74019369a95097b2e4faf89 Mon Sep 17 00:00:00 2001 From: Eduardo Dorantes Date: Mon, 9 Nov 2026 08:36:33 -0600 Subject: [PATCH 3/5] visionOS: PBR normal, metallic-roughness, occlusion and emissive maps --- ViroRenderer/Shaders.metal | 95 ++++++++++++++++++++-- ViroRenderer/VROGeometrySubstrateMetal.cpp | 8 ++ ViroRenderer/VROMaterialSubstrateMetal.cpp | 36 +++++++- ViroRenderer/VROMaterialSubstrateMetal.h | 10 +++ ViroRenderer/VROSharedStructures.h | 16 ++++ 5 files changed, 156 insertions(+), 9 deletions(-) diff --git a/ViroRenderer/Shaders.metal b/ViroRenderer/Shaders.metal index 8db7c0f25..6b1f777d4 100644 --- a/ViroRenderer/Shaders.metal +++ b/ViroRenderer/Shaders.metal @@ -1440,6 +1440,8 @@ typedef struct { float3 normal; float3 ambient_color; + // The untransformed texture coordinates, for a map with its own transform (emissive). + float2 raw_texcoord; // Particle instancing: which instance this vertex belongs to, so the fragment // stage can index particle_colors. Flat — an instance index must not interpolate. uint instance_id [[ flat ]]; @@ -1490,6 +1492,7 @@ vertex VROPBRLightingVertexOut pbr_lighting_vertex(VRORendererAttributes attribu out.surface_position = v_position; out.camera_position = view.camera_position; out.normal = normalize((_transforms.normal_matrix * float4(_geometry.normal, 0.0)).xyz); + out.raw_texcoord = _geometry.texcoord; out.ambient_color = lighting.ambient_light_color; return out; @@ -1498,6 +1501,11 @@ vertex VROPBRLightingVertexOut pbr_lighting_vertex(VRORendererAttributes attribu // Shared body: `albedo` already carries the material colour (from a uniform or a texture). static float4 vro_pbr_shade(VROPBRLightingVertexOut in, float4 albedo, + float3 normal, + float metalness, + float roughness, + float ao, + float3 emissive, constant VROMaterialUniforms &material, constant VROSceneLightingUniforms &lighting, depth2d_array shadow_map, @@ -1511,11 +1519,11 @@ static float4 vro_pbr_shade(VROPBRLightingVertexOut in, _surface.specular_texcoord = in.texcoord; _surface.shininess = material.shininess; // Clamped: a roughness of zero makes the GGX denominator collapse. - _surface.roughness = max(material.roughness, 0.06); - _surface.metalness = material.metalness; - _surface.ao = material.ao; + _surface.roughness = max(roughness, 0.06); + _surface.metalness = metalness; + _surface.ao = ao; _surface.alpha = material.alpha; - _surface.normal = normalize(in.normal); + _surface.normal = normalize(normal); _surface.position = in.surface_position; _surface.view = normalize(in.camera_position - in.surface_position); @@ -1579,10 +1587,12 @@ static float4 vro_pbr_shade(VROPBRLightingVertexOut in, ambient = (kD * diffuse_ibl + specular_ibl) * _surface.ao; } else { - ambient = in.ambient_color * base_color * _surface.ao; + // A metal has no diffuse response, and without an environment map nothing to + // reflect: only the diffusing share of the surface takes the flat ambient. + ambient = in.ambient_color * base_color * (1.0 - _surface.metalness) * _surface.ao; } - return float4(ambient + radiance_out + material.emissive_color, _surface.alpha * _surface.diffuse_color.a); + return float4(ambient + radiance_out + emissive, _surface.alpha * _surface.diffuse_color.a); } fragment VROLightingFragmentOut pbr_lighting_fragment_c(VROPBRLightingVertexOut in [[ stage_in ]], @@ -1594,7 +1604,8 @@ fragment VROLightingFragmentOut pbr_lighting_fragment_c(VROPBRLightingVertexOut texturecube irradiance_map [[ texture(5) ]], texturecube prefiltered_map [[ texture(6) ]], texture2d brdf_map [[ texture(7) ]]) { - const float4 color = vro_pbr_shade(in, material.diffuse_surface_color, material, lighting, shadow_map, + const float4 color = vro_pbr_shade(in, material.diffuse_surface_color, in.normal, material.metalness, + material.roughness, material.ao, material.emissive_color, material, lighting, shadow_map, irradiance_map, prefiltered_map, brdf_map); return VROMakeLightingOut(color, material); } @@ -1610,11 +1621,79 @@ fragment VROLightingFragmentOut pbr_lighting_fragment_t(VROPBRLightingVertexOut texturecube prefiltered_map [[ texture(6) ]], texture2d brdf_map [[ texture(7) ]]) { const float4 albedo = diffuse_texture.sample(s, in.texcoord) * material.diffuse_surface_color; - const float4 color = vro_pbr_shade(in, albedo, material, lighting, shadow_map, + const float4 color = vro_pbr_shade(in, albedo, in.normal, material.metalness, material.roughness, + material.ao, material.emissive_color, material, lighting, shadow_map, irradiance_map, prefiltered_map, brdf_map); return VROMakeLightingOut(color, material); } +// Tangent frame from screen-space derivatives (no tangent attribute needed): T and B +// follow +u and +v on the surface. glTF normal maps put +Y toward -v (up in the image), +// so the map's green is flipped before it is applied. +static float3x3 vro_cotangent_frame(float3 N, float3 p, float2 uv) { + const float3 dp1 = dfdx(p); + const float3 dp2 = dfdy(p); + const float2 duv1 = dfdx(uv); + const float2 duv2 = dfdy(uv); + const float3 dp2perp = cross(dp2, N); + const float3 dp1perp = cross(N, dp1); + const float3 T = dp2perp * duv1.x + dp1perp * duv2.x; + const float3 B = dp2perp * duv1.y + dp1perp * duv2.y; + const float invmax = rsqrt(max(max(dot(T, T), dot(B, B)), 1e-12)); + return float3x3(T * invmax, B * invmax, N); +} + +// PBR with glTF's maps: base color, normal (scaled), metallic-roughness (B and G, by the +// factors), occlusion (R, by its strength) and emissive (by the emissive color, through +// its own texture transform). A map that is absent is bound to the blank texture and +// skipped through material.pbr_maps. +fragment VROLightingFragmentOut pbr_lighting_fragment_maps(VROPBRLightingVertexOut in [[ stage_in ]], + texture2d diffuse_texture [[ texture(0) ]], + texture2d normal_map [[ texture(8) ]], + texture2d metal_rough_map [[ texture(9) ]], + texture2d ao_map [[ texture(10) ]], + texture2d emissive_map [[ texture(11) ]], + constant VROMaterialUniforms &material [[ buffer(2) ]], + constant VROCustomUniforms &_custom [[ buffer(3) ]], + constant VROSceneLightingUniforms &lighting [[ buffer(4) ]], + constant float4 *particle_colors [[ buffer(7) ]], + depth2d_array shadow_map [[ texture(4) ]], + texturecube irradiance_map [[ texture(5) ]], + texturecube prefiltered_map [[ texture(6) ]], + texture2d brdf_map [[ texture(7) ]]) { + const int maps = material.pbr_maps; + float4 albedo = material.diffuse_surface_color; + if (maps & VRO_PBR_MAP_BASE) { + albedo *= diffuse_texture.sample(s, in.texcoord); + } + float3 N = normalize(in.normal); + if (maps & VRO_PBR_MAP_NORMAL) { + float3 t = normal_map.sample(s, in.texcoord).xyz * 2.0 - 1.0; + t.xy *= material.normal_scale; + t.y = -t.y; + N = normalize(vro_cotangent_frame(N, in.surface_position, in.texcoord) * t); + } + float metalness = material.metalness; + float roughness = material.roughness; + if (maps & VRO_PBR_MAP_METAL_ROUGH) { + const float4 mr = metal_rough_map.sample(s, in.texcoord); + metalness = mr.b * material.metalness_intensity; + roughness = mr.g * material.roughness_intensity; + } + float ao = material.ao; + if (maps & VRO_PBR_MAP_AO) { + ao = mix(1.0, ao_map.sample(s, in.texcoord).r, material.ao_strength); + } + float3 emissive = material.emissive_color; + if (maps & VRO_PBR_MAP_EMISSIVE) { + const float2 uv = (material.emissive_texture_transform * float4(in.raw_texcoord, 0.0, 1.0)).xy; + emissive *= emissive_map.sample(s, uv).rgb; + } + const float4 color = vro_pbr_shade(in, albedo, N, metalness, roughness, ao, emissive, material, lighting, + shadow_map, irradiance_map, prefiltered_map, brdf_map); + return VROMakeLightingOut(color, material); +} + vertex VRODistortionVertexOut distortion_vertex(VRODistortionAttributes attributes [[ stage_in ]], constant VRODistortionUniforms &uniforms [[ buffer(1) ]]) { diff --git a/ViroRenderer/VROGeometrySubstrateMetal.cpp b/ViroRenderer/VROGeometrySubstrateMetal.cpp index 8037e0017..e24b9bf56 100644 --- a/ViroRenderer/VROGeometrySubstrateMetal.cpp +++ b/ViroRenderer/VROGeometrySubstrateMetal.cpp @@ -981,6 +981,14 @@ void VROGeometrySubstrateMetal::renderMaterial(VROMaterialSubstrateMetal *materi [renderEncoder setFragmentTexture:substrate->getTexture() atIndex:j]; } + for (const std::pair> &indexed : material->getIndexedTextures()) { + VROTextureSubstrateMetal *substrate = (VROTextureSubstrateMetal *) indexed.second->getSubstrate(0, driver, true); + if (!substrate) { + std::shared_ptr blank = getBlankTexture(VROTextureType::Texture2D); + substrate = (VROTextureSubstrateMetal *) blank->getSubstrate(0, driver, true); + } + [renderEncoder setFragmentTexture:substrate->getTexture() atIndex:indexed.first]; + } [renderEncoder drawIndexedPrimitives:element.primitiveType indexCount:element.indexCount diff --git a/ViroRenderer/VROMaterialSubstrateMetal.cpp b/ViroRenderer/VROMaterialSubstrateMetal.cpp index 71efe6a24..64ae0a78d 100644 --- a/ViroRenderer/VROMaterialSubstrateMetal.cpp +++ b/ViroRenderer/VROMaterialSubstrateMetal.cpp @@ -28,6 +28,7 @@ #if VRO_METAL #include "VROSharedStructures.h" +#include "VROImageUtil.h" #include "VROMetalUtils.h" #include "VROShaderModifier.h" #include "VRODriverMetal.h" @@ -403,6 +404,20 @@ void VROMaterialSubstrateMetal::loadPhongLighting(const VROMaterial &material, _program = getPooledShader(vertexProgram, fragmentProgram, library); } +int VROMaterialSubstrateMetal::pbrMaps(const VROMaterial &material) { + // Which PBR maps a material has. Occlusion on a second UV set is left out: the Metal + // path reads one set of texture coordinates. + int maps = 0; + if (material.getDiffuse().getTextureType() == VROTextureType::Texture2D) maps |= VRO_PBR_MAP_BASE; + if (material.getNormal().getTextureType() == VROTextureType::Texture2D) maps |= VRO_PBR_MAP_NORMAL; + if (material.getRoughness().getTextureType() == VROTextureType::Texture2D || + material.getMetalness().getTextureType() == VROTextureType::Texture2D) maps |= VRO_PBR_MAP_METAL_ROUGH; + if (material.getAmbientOcclusion().getTextureType() == VROTextureType::Texture2D && + material.getAmbientOcclusion().getTexcoordSet() == 0) maps |= VRO_PBR_MAP_AO; + if (material.getEmission().getTextureType() == VROTextureType::Texture2D) maps |= VRO_PBR_MAP_EMISSIVE; + return maps; +} + void VROMaterialSubstrateMetal::loadPBRLighting(const VROMaterial &material, id library, id device, VRODriverMetal &driver) { @@ -410,7 +425,20 @@ void VROMaterialSubstrateMetal::loadPBRLighting(const VROMaterial &material, std::string fragmentProgram; VROMaterialVisual &diffuse = material.getDiffuse(); - if (diffuse.getTextureType() == VROTextureType::None) { + int maps = pbrMaps(material); + if ((maps & ~VRO_PBR_MAP_BASE) != 0) { + // glTF maps: the base color at 0 (blank when absent), the others at 8-11. + fragmentProgram = "pbr_lighting_fragment_maps"; + std::shared_ptr blank = getBlankTexture(VROTextureType::Texture2D); + auto mapOrBlank = [&](VROMaterialVisual &visual, int bit) { + return (maps & bit) ? visual.getTexture() : blank; + }; + _textures.push_back(mapOrBlank(diffuse, VRO_PBR_MAP_BASE)); + _indexedTextures.push_back({ 8, mapOrBlank(material.getNormal(), VRO_PBR_MAP_NORMAL) }); + _indexedTextures.push_back({ 9, mapOrBlank(material.getRoughness(), VRO_PBR_MAP_METAL_ROUGH) }); + _indexedTextures.push_back({ 10, mapOrBlank(material.getAmbientOcclusion(), VRO_PBR_MAP_AO) }); + _indexedTextures.push_back({ 11, mapOrBlank(material.getEmission(), VRO_PBR_MAP_EMISSIVE) }); + } else if (diffuse.getTextureType() == VROTextureType::None) { fragmentProgram = "pbr_lighting_fragment_c"; } else { fragmentProgram = "pbr_lighting_fragment_t"; @@ -488,6 +516,12 @@ VROConcurrentBuffer &VROMaterialSubstrateMetal::bindMaterialUniforms(float opaci VROVector4f emission = _material.getEmission().getColor(); uniforms->emissive_color = toVectorFloat3(VROVector3f(emission.x, emission.y, emission.z)); uniforms->ignores_diffuse_alpha = _material.getIgnoresDiffuseAlpha() ? 1 : 0; + uniforms->pbr_maps = pbrMaps(_material); + uniforms->metalness_intensity = _material.getMetalness().getIntensity(); + uniforms->roughness_intensity = _material.getRoughness().getIntensity(); + uniforms->normal_scale = _material.getNormal().getIntensity(); + uniforms->ao_strength = _material.getAmbientOcclusion().getIntensity(); + uniforms->emissive_texture_transform = toMatrixFloat4x4(_material.getEmission().getContentsTransform()); // Fill custom uniforms buffer based on the layout created during inflation if (!_material.getShaderModifiers().empty()) { diff --git a/ViroRenderer/VROMaterialSubstrateMetal.h b/ViroRenderer/VROMaterialSubstrateMetal.h index 9717e5518..657613b7d 100644 --- a/ViroRenderer/VROMaterialSubstrateMetal.h +++ b/ViroRenderer/VROMaterialSubstrateMetal.h @@ -104,6 +104,13 @@ class VROMaterialSubstrateMetal : public VROMaterialSubstrate { const std::vector> &getTextures() const { return _textures; } + /* + Textures bound at a fixed fragment index rather than in order (the PBR maps, at 8-11, + past the shadow and IBL textures at 4-7). + */ + const std::vector>> &getIndexedTextures() const { + return _indexedTextures; + } VROConcurrentBuffer *getCustomUniformsBuffer() const { return _customUniformsBuffer; @@ -133,6 +140,8 @@ class VROMaterialSubstrateMetal : public VROMaterialSubstrate { void loadPhongLighting(const VROMaterial &material, id library, id device, VRODriverMetal &driver); + // VRO_PBR_MAP_* bits for the maps a material has. + static int pbrMaps(const VROMaterial &material); void loadPBRLighting(const VROMaterial &material, id library, id device, VRODriverMetal &driver); @@ -157,6 +166,7 @@ class VROMaterialSubstrateMetal : public VROMaterialSubstrate { VROConcurrentBuffer *_customUniformsBuffer; std::vector> _textures; + std::vector>> _indexedTextures; void bindConstantLighting(const std::shared_ptr &light); void bindBlinnLighting(const std::shared_ptr &light); diff --git a/ViroRenderer/VROSharedStructures.h b/ViroRenderer/VROSharedStructures.h index 10a0d5ff1..dbd4febe0 100644 --- a/ViroRenderer/VROSharedStructures.h +++ b/ViroRenderer/VROSharedStructures.h @@ -88,8 +88,24 @@ typedef struct { matrix_float4x4 texture_transform; vector_float3 emissive_color; int ignores_diffuse_alpha; + + // PBR maps (pbr_lighting_fragment_maps): which are bound (VROPBRMap bits), and the + // factors glTF scales them by. The emissive map has its own texture transform. + int pbr_maps; + float metalness_intensity; + float roughness_intensity; + float normal_scale; + float ao_strength; + matrix_float4x4 emissive_texture_transform; } VROMaterialUniforms; +// Bits of VROMaterialUniforms.pbr_maps. +#define VRO_PBR_MAP_BASE 1 +#define VRO_PBR_MAP_NORMAL 2 +#define VRO_PBR_MAP_METAL_ROUGH 4 +#define VRO_PBR_MAP_AO 8 +#define VRO_PBR_MAP_EMISSIVE 16 + typedef struct { simd_float3 position; simd_float3 normal; From 0b26505da64509cb1460cfd6c1ac54ce40f5a162 Mon Sep 17 00:00:00 2001 From: Benito Mota Date: Mon, 9 Nov 2026 08:52:04 -0600 Subject: [PATCH 4/5] visionOS test app builds again: shared space sources, ViroReact stub module, MetalKit and Accelerate, bundled models --- .../App/ViroKitVisionOSTestApp.swift | 3 + .../App/ViroReactStub/module.modulemap | 6 + .../App/ViroShadersSource.txt | 111 ++++- .../project.pbxproj | 391 ++++++++++-------- .../xcschemes/ViroKitVisionOSTest.xcscheme | 4 +- ios/create_visionos_test_app.rb | 27 +- 6 files changed, 345 insertions(+), 197 deletions(-) create mode 100644 ios/ViroKitVisionOSTest/App/ViroReactStub/module.modulemap diff --git a/ios/ViroKitVisionOSTest/App/ViroKitVisionOSTestApp.swift b/ios/ViroKitVisionOSTest/App/ViroKitVisionOSTestApp.swift index 45e720ac8..eff8f674b 100644 --- a/ios/ViroKitVisionOSTest/App/ViroKitVisionOSTestApp.swift +++ b/ios/ViroKitVisionOSTest/App/ViroKitVisionOSTestApp.swift @@ -9,6 +9,9 @@ struct ViroKitVisionOSTestApp: App { ContentView() .viroImmersiveSpaceController() } + // A small window, so it does not hide the test scene behind it. + .defaultSize(width: 520, height: 260) + .windowResizability(.contentSize) ImmersiveSpace(id: ViroImmersiveSpace.id) { ViroImmersiveSpaceView() diff --git a/ios/ViroKitVisionOSTest/App/ViroReactStub/module.modulemap b/ios/ViroKitVisionOSTest/App/ViroReactStub/module.modulemap new file mode 100644 index 000000000..e8b496145 --- /dev/null +++ b/ios/ViroKitVisionOSTest/App/ViroReactStub/module.modulemap @@ -0,0 +1,6 @@ +// A stand-in for the ViroReact pod's module, so ViroSharedSpace.swift (which imports it) +// builds in this test app. It exposes only the header that file uses. +module ViroReact { + header "../../../../../viro/ios/ViroReact/VisionOS/VROSharedSpaceBridge.h" + export * +} diff --git a/ios/ViroKitVisionOSTest/App/ViroShadersSource.txt b/ios/ViroKitVisionOSTest/App/ViroShadersSource.txt index 08f2f2735..7ebd36cda 100644 --- a/ios/ViroKitVisionOSTest/App/ViroShadersSource.txt +++ b/ios/ViroKitVisionOSTest/App/ViroShadersSource.txt @@ -81,8 +81,24 @@ typedef struct { matrix_float4x4 texture_transform; vector_float3 emissive_color; int ignores_diffuse_alpha; + + // PBR maps (pbr_lighting_fragment_maps): which are bound (VROPBRMap bits), and the + // factors glTF scales them by. The emissive map has its own texture transform. + int pbr_maps; + float metalness_intensity; + float roughness_intensity; + float normal_scale; + float ao_strength; + matrix_float4x4 emissive_texture_transform; } VROMaterialUniforms; +// Bits of VROMaterialUniforms.pbr_maps. +#define VRO_PBR_MAP_BASE 1 +#define VRO_PBR_MAP_NORMAL 2 +#define VRO_PBR_MAP_METAL_ROUGH 4 +#define VRO_PBR_MAP_AO 8 +#define VRO_PBR_MAP_EMISSIVE 16 + typedef struct { simd_float3 position; simd_float3 normal; @@ -1549,6 +1565,8 @@ typedef struct { float3 normal; float3 ambient_color; + // The untransformed texture coordinates, for a map with its own transform (emissive). + float2 raw_texcoord; // Particle instancing: which instance this vertex belongs to, so the fragment // stage can index particle_colors. Flat — an instance index must not interpolate. uint instance_id [[ flat ]]; @@ -1599,6 +1617,7 @@ vertex VROPBRLightingVertexOut pbr_lighting_vertex(VRORendererAttributes attribu out.surface_position = v_position; out.camera_position = view.camera_position; out.normal = normalize((_transforms.normal_matrix * float4(_geometry.normal, 0.0)).xyz); + out.raw_texcoord = _geometry.texcoord; out.ambient_color = lighting.ambient_light_color; return out; @@ -1607,6 +1626,11 @@ vertex VROPBRLightingVertexOut pbr_lighting_vertex(VRORendererAttributes attribu // Shared body: `albedo` already carries the material colour (from a uniform or a texture). static float4 vro_pbr_shade(VROPBRLightingVertexOut in, float4 albedo, + float3 normal, + float metalness, + float roughness, + float ao, + float3 emissive, constant VROMaterialUniforms &material, constant VROSceneLightingUniforms &lighting, depth2d_array shadow_map, @@ -1620,11 +1644,11 @@ static float4 vro_pbr_shade(VROPBRLightingVertexOut in, _surface.specular_texcoord = in.texcoord; _surface.shininess = material.shininess; // Clamped: a roughness of zero makes the GGX denominator collapse. - _surface.roughness = max(material.roughness, 0.06); - _surface.metalness = material.metalness; - _surface.ao = material.ao; + _surface.roughness = max(roughness, 0.06); + _surface.metalness = metalness; + _surface.ao = ao; _surface.alpha = material.alpha; - _surface.normal = normalize(in.normal); + _surface.normal = normalize(normal); _surface.position = in.surface_position; _surface.view = normalize(in.camera_position - in.surface_position); @@ -1688,10 +1712,12 @@ static float4 vro_pbr_shade(VROPBRLightingVertexOut in, ambient = (kD * diffuse_ibl + specular_ibl) * _surface.ao; } else { - ambient = in.ambient_color * base_color * _surface.ao; + // A metal has no diffuse response, and without an environment map nothing to + // reflect: only the diffusing share of the surface takes the flat ambient. + ambient = in.ambient_color * base_color * (1.0 - _surface.metalness) * _surface.ao; } - return float4(ambient + radiance_out + material.emissive_color, _surface.alpha * _surface.diffuse_color.a); + return float4(ambient + radiance_out + emissive, _surface.alpha * _surface.diffuse_color.a); } fragment VROLightingFragmentOut pbr_lighting_fragment_c(VROPBRLightingVertexOut in [[ stage_in ]], @@ -1703,7 +1729,8 @@ fragment VROLightingFragmentOut pbr_lighting_fragment_c(VROPBRLightingVertexOut texturecube irradiance_map [[ texture(5) ]], texturecube prefiltered_map [[ texture(6) ]], texture2d brdf_map [[ texture(7) ]]) { - const float4 color = vro_pbr_shade(in, material.diffuse_surface_color, material, lighting, shadow_map, + const float4 color = vro_pbr_shade(in, material.diffuse_surface_color, in.normal, material.metalness, + material.roughness, material.ao, material.emissive_color, material, lighting, shadow_map, irradiance_map, prefiltered_map, brdf_map); return VROMakeLightingOut(color, material); } @@ -1719,11 +1746,79 @@ fragment VROLightingFragmentOut pbr_lighting_fragment_t(VROPBRLightingVertexOut texturecube prefiltered_map [[ texture(6) ]], texture2d brdf_map [[ texture(7) ]]) { const float4 albedo = diffuse_texture.sample(s, in.texcoord) * material.diffuse_surface_color; - const float4 color = vro_pbr_shade(in, albedo, material, lighting, shadow_map, + const float4 color = vro_pbr_shade(in, albedo, in.normal, material.metalness, material.roughness, + material.ao, material.emissive_color, material, lighting, shadow_map, irradiance_map, prefiltered_map, brdf_map); return VROMakeLightingOut(color, material); } +// Tangent frame from screen-space derivatives (no tangent attribute needed): T and B +// follow +u and +v on the surface. glTF normal maps put +Y toward -v (up in the image), +// so the map's green is flipped before it is applied. +static float3x3 vro_cotangent_frame(float3 N, float3 p, float2 uv) { + const float3 dp1 = dfdx(p); + const float3 dp2 = dfdy(p); + const float2 duv1 = dfdx(uv); + const float2 duv2 = dfdy(uv); + const float3 dp2perp = cross(dp2, N); + const float3 dp1perp = cross(N, dp1); + const float3 T = dp2perp * duv1.x + dp1perp * duv2.x; + const float3 B = dp2perp * duv1.y + dp1perp * duv2.y; + const float invmax = rsqrt(max(max(dot(T, T), dot(B, B)), 1e-12)); + return float3x3(T * invmax, B * invmax, N); +} + +// PBR with glTF's maps: base color, normal (scaled), metallic-roughness (B and G, by the +// factors), occlusion (R, by its strength) and emissive (by the emissive color, through +// its own texture transform). A map that is absent is bound to the blank texture and +// skipped through material.pbr_maps. +fragment VROLightingFragmentOut pbr_lighting_fragment_maps(VROPBRLightingVertexOut in [[ stage_in ]], + texture2d diffuse_texture [[ texture(0) ]], + texture2d normal_map [[ texture(8) ]], + texture2d metal_rough_map [[ texture(9) ]], + texture2d ao_map [[ texture(10) ]], + texture2d emissive_map [[ texture(11) ]], + constant VROMaterialUniforms &material [[ buffer(2) ]], + constant VROCustomUniforms &_custom [[ buffer(3) ]], + constant VROSceneLightingUniforms &lighting [[ buffer(4) ]], + constant float4 *particle_colors [[ buffer(7) ]], + depth2d_array shadow_map [[ texture(4) ]], + texturecube irradiance_map [[ texture(5) ]], + texturecube prefiltered_map [[ texture(6) ]], + texture2d brdf_map [[ texture(7) ]]) { + const int maps = material.pbr_maps; + float4 albedo = material.diffuse_surface_color; + if (maps & VRO_PBR_MAP_BASE) { + albedo *= diffuse_texture.sample(s, in.texcoord); + } + float3 N = normalize(in.normal); + if (maps & VRO_PBR_MAP_NORMAL) { + float3 t = normal_map.sample(s, in.texcoord).xyz * 2.0 - 1.0; + t.xy *= material.normal_scale; + t.y = -t.y; + N = normalize(vro_cotangent_frame(N, in.surface_position, in.texcoord) * t); + } + float metalness = material.metalness; + float roughness = material.roughness; + if (maps & VRO_PBR_MAP_METAL_ROUGH) { + const float4 mr = metal_rough_map.sample(s, in.texcoord); + metalness = mr.b * material.metalness_intensity; + roughness = mr.g * material.roughness_intensity; + } + float ao = material.ao; + if (maps & VRO_PBR_MAP_AO) { + ao = mix(1.0, ao_map.sample(s, in.texcoord).r, material.ao_strength); + } + float3 emissive = material.emissive_color; + if (maps & VRO_PBR_MAP_EMISSIVE) { + const float2 uv = (material.emissive_texture_transform * float4(in.raw_texcoord, 0.0, 1.0)).xy; + emissive *= emissive_map.sample(s, uv).rgb; + } + const float4 color = vro_pbr_shade(in, albedo, N, metalness, roughness, ao, emissive, material, lighting, + shadow_map, irradiance_map, prefiltered_map, brdf_map); + return VROMakeLightingOut(color, material); +} + vertex VRODistortionVertexOut distortion_vertex(VRODistortionAttributes attributes [[ stage_in ]], constant VRODistortionUniforms &uniforms [[ buffer(1) ]]) { diff --git a/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/project.pbxproj b/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/project.pbxproj index af47e3597..812400e67 100644 --- a/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/project.pbxproj +++ b/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/project.pbxproj @@ -3,36 +3,37 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ - 09246380CF0B9F80FAA62036 /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 39A5876AE5C9C8A7F0A0640C /* Shaders.metal */; }; - 14493EB06DC2CF09C1ADCBA5 /* VRORendererBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2D8275BE5EBE1FCCCC9243D /* VRORendererBridge.mm */; }; - 170DBA9C1706F872A13EF10D /* ViroKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF6CA2E5DBEA40C5563D2B44 /* ViroKit.xcframework */; }; - 1FED144A51634F8404989768 /* ViroKitVisionOSTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E437D45A3FD76561F7905 /* ViroKitVisionOSTestApp.swift */; }; - 2FAD3A7960EC6BB773D31FAD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A8B96073B0F338F063303CD /* Foundation.framework */; }; - 3D28B8ABAB0E726934723067 /* ViroImmersiveRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83373494ECFFB003D8250D1C /* ViroImmersiveRenderer.swift */; }; - 54F8FAA93F6E49038D41216A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F0D12727799D8549A697BB /* ContentView.swift */; }; - 87A6671082280ABE2ED3122D /* ViroImmersiveSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220A2746ABFED3E4D24F80A3 /* ViroImmersiveSpace.swift */; }; - AA11BB22CC33DD44EE55FF00 /* shiba.glb in Resources */ = {isa = PBXBuildFile; fileRef = BB22CC33DD44EE55FF00AA11 /* shiba.glb */; }; - AA22BB33CC44DD55EE66FF00 /* ViroShadersSource.txt in Resources */ = {isa = PBXBuildFile; fileRef = BB33CC44DD55EE66FF00AA22 /* ViroShadersSource.txt */; }; - CC33DD44EE55FF00AA11BB22 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD44EE55FF00AA11BB22CC33 /* Accelerate.framework */; }; - DD44EE55FF11AA22BB33CC00 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE55FF00AA11BB22CC33DD44 /* MetalKit.framework */; }; - EE11FF22AA33BB44CC55DD66 /* Soldier.glb in Resources */ = {isa = PBXBuildFile; fileRef = FF22AA33BB44CC55DD66EE11 /* Soldier.glb */; }; - F2D88EADF893A3BB695FCEB1 /* ViroKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AF6CA2E5DBEA40C5563D2B44 /* ViroKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - F40FB32E411CED8B4632BB82 /* ViroImmersiveCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80360FEA3D6EF6BC0017D88B /* ViroImmersiveCoordinator.swift */; }; - FF33AA44BB55CC66DD77EE00 /* 360_space.jpg in Resources */ = {isa = PBXBuildFile; fileRef = EE33FF44AA55BB66CC77DD00 /* 360_space.jpg */; }; + 1AB87DCCAE756077B52D0244 /* ViroImmersiveSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C869239B38DA8B4DBE6981 /* ViroImmersiveSpace.swift */; }; + 1D7C596954F8B884AA998CDF /* ViroImmersiveCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0343B9DF13ED7F476858B9 /* ViroImmersiveCoordinator.swift */; }; + 37A8FFBE20C92DDAC2134400 /* ViroKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09FF92C372F8D731D4E166A7 /* ViroKit.xcframework */; }; + 387D6379A4C0E85787BDAC3A /* VRORendererBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 172ADAE73BDCFB08E07AA330 /* VRORendererBridge.mm */; }; + 46327BA743A2118A0F122A07 /* Soldier.glb in Resources */ = {isa = PBXBuildFile; fileRef = E7FE2AABBCA56FBEDA805DA4 /* Soldier.glb */; }; + 4ECDB55704DC6991189F6758 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B49338AA8A683EB1A8BC216 /* Foundation.framework */; }; + 51ABCA6A04D656C8C3BF1519 /* ViroKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09FF92C372F8D731D4E166A7 /* ViroKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 62EE1C58610699BCAF1AD5BB /* VROSharedSpaceBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = E568A88357B0C4703ADB49E0 /* VROSharedSpaceBridge.mm */; }; + 632FBEFFA03EC631E172414A /* ViroShadersSource.txt in Resources */ = {isa = PBXBuildFile; fileRef = F9204880165ECC3C73490777 /* ViroShadersSource.txt */; }; + 6F95659E2B80FB6D2C3EB5F8 /* 360_space.jpg in Resources */ = {isa = PBXBuildFile; fileRef = E822F6D43D6EF9C8CB3041D2 /* 360_space.jpg */; }; + 85E7373405DE7164B5D05DBB /* shiba.glb in Resources */ = {isa = PBXBuildFile; fileRef = 651818D080CD2CF7A0D19817 /* shiba.glb */; }; + 92205ACCC6B87C32DD0F57F4 /* DamagedHelmet.glb in Resources */ = {isa = PBXBuildFile; fileRef = 12FB37DAACB265348A211265 /* DamagedHelmet.glb */; }; + 9FD16B6E22D64342C69F0970 /* ViroSharedSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 333F06DF489EF9A387E5D4C9 /* ViroSharedSpace.swift */; }; + B0203B25FA68DBB4EC59E6EF /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 4CF19442C4E30C43FC324E11 /* Shaders.metal */; }; + BF546BAFFE2A8363C13F7E23 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1384E6F46E1CA009E4CF9C0E /* ContentView.swift */; }; + D19D0155F86CC348A8C46363 /* ViroImmersiveRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF83DEBBF80477B516F6BB18 /* ViroImmersiveRenderer.swift */; }; + DAB8C3312D52C62327C1A22E /* ViroKitVisionOSTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97E80775E11EA68E813E42B /* ViroKitVisionOSTestApp.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - 664EF48F72E909D2BA20C617 /* Embed Frameworks */ = { + C422AB8F57093A82996D6C4A /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - F2D88EADF893A3BB695FCEB1 /* ViroKit.xcframework in Embed Frameworks */, + 51ABCA6A04D656C8C3BF1519 /* ViroKit.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -40,136 +41,141 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 220A2746ABFED3E4D24F80A3 /* ViroImmersiveSpace.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroImmersiveSpace.swift; sourceTree = ""; }; - 39A5876AE5C9C8A7F0A0640C /* Shaders.metal */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = ""; }; - 657CBA5513FCE67545DDBD75 /* ViroKitVisionOSTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ViroKitVisionOSTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 7EB2A45A72A550C394486E9A /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 80360FEA3D6EF6BC0017D88B /* ViroImmersiveCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroImmersiveCoordinator.swift; sourceTree = ""; }; - 83373494ECFFB003D8250D1C /* ViroImmersiveRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroImmersiveRenderer.swift; sourceTree = ""; }; - 838017A544325DD2C573C5C0 /* BridgingHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; - 87F0D12727799D8549A697BB /* ContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 8A8B96073B0F338F063303CD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - AF6CA2E5DBEA40C5563D2B44 /* ViroKit.xcframework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.xcframework; name = ViroKit.xcframework; path = ../../../viro/ios/dist/ViroRendererVisionOS/ViroKit.xcframework; sourceTree = ""; }; - B2D8275BE5EBE1FCCCC9243D /* VRORendererBridge.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = VRORendererBridge.mm; sourceTree = ""; }; - BB22CC33DD44EE55FF00AA11 /* shiba.glb */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; path = shiba.glb; sourceTree = ""; }; - BB33CC44DD55EE66FF00AA22 /* ViroShadersSource.txt */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = ViroShadersSource.txt; sourceTree = ""; }; - BD4A175078D88E89F9FA9977 /* VRORendererBridge.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = VRORendererBridge.h; sourceTree = ""; }; - DD44EE55FF00AA11BB22CC33 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; - EA5E437D45A3FD76561F7905 /* ViroKitVisionOSTestApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroKitVisionOSTestApp.swift; sourceTree = ""; }; - EE33FF44AA55BB66CC77DD00 /* 360_space.jpg */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = image.jpeg; path = 360_space.jpg; sourceTree = ""; }; - EE55FF00AA11BB22CC33DD44 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; - FF22AA33BB44CC55DD66EE11 /* Soldier.glb */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; path = Soldier.glb; sourceTree = ""; }; + 05A802A93D9BE6D9030B5E9D /* VROSharedSpaceBridge.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = VROSharedSpaceBridge.h; sourceTree = ""; }; + 09FF92C372F8D731D4E166A7 /* ViroKit.xcframework */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = wrapper.xcframework; name = ViroKit.xcframework; path = ../../../viro/ios/dist/ViroRendererVisionOS/ViroKit.xcframework; sourceTree = ""; }; + 0B49338AA8A683EB1A8BC216 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS26.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 0F73C5844E1C2E4ED917AC07 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 12FB37DAACB265348A211265 /* DamagedHelmet.glb */ = {isa = PBXFileReference; includeInIndex = 1; path = DamagedHelmet.glb; sourceTree = ""; }; + 1384E6F46E1CA009E4CF9C0E /* ContentView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 147A65117E53C48B49FEE53A /* module.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; name = module.modulemap; path = ViroReactStub/module.modulemap; sourceTree = ""; }; + 172ADAE73BDCFB08E07AA330 /* VRORendererBridge.mm */ = {isa = PBXFileReference; includeInIndex = 1; path = VRORendererBridge.mm; sourceTree = ""; }; + 333F06DF489EF9A387E5D4C9 /* ViroSharedSpace.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroSharedSpace.swift; sourceTree = ""; }; + 4CF19442C4E30C43FC324E11 /* Shaders.metal */ = {isa = PBXFileReference; includeInIndex = 1; path = Shaders.metal; sourceTree = ""; }; + 651818D080CD2CF7A0D19817 /* shiba.glb */ = {isa = PBXFileReference; includeInIndex = 1; path = shiba.glb; sourceTree = ""; }; + 6B5250943694E5F813F8C159 /* VRORendererBridge.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = VRORendererBridge.h; sourceTree = ""; }; + 85BBE9BF03A472571062704F /* BridgingHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; + 9E0343B9DF13ED7F476858B9 /* ViroImmersiveCoordinator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroImmersiveCoordinator.swift; sourceTree = ""; }; + A97E80775E11EA68E813E42B /* ViroKitVisionOSTestApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroKitVisionOSTestApp.swift; sourceTree = ""; }; + BC55F33CB214259A7ABE258B /* ViroKitVisionOSTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ViroKitVisionOSTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C4C869239B38DA8B4DBE6981 /* ViroImmersiveSpace.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroImmersiveSpace.swift; sourceTree = ""; }; + E568A88357B0C4703ADB49E0 /* VROSharedSpaceBridge.mm */ = {isa = PBXFileReference; includeInIndex = 1; path = VROSharedSpaceBridge.mm; sourceTree = ""; }; + E7FE2AABBCA56FBEDA805DA4 /* Soldier.glb */ = {isa = PBXFileReference; includeInIndex = 1; path = Soldier.glb; sourceTree = ""; }; + E822F6D43D6EF9C8CB3041D2 /* 360_space.jpg */ = {isa = PBXFileReference; includeInIndex = 1; path = 360_space.jpg; sourceTree = ""; }; + EF83DEBBF80477B516F6BB18 /* ViroImmersiveRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ViroImmersiveRenderer.swift; sourceTree = ""; }; + F9204880165ECC3C73490777 /* ViroShadersSource.txt */ = {isa = PBXFileReference; includeInIndex = 1; path = ViroShadersSource.txt; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 4B25984028BE59690CC47ECC /* Frameworks */ = { + E6BB443E0799B3C58E1F8805 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2FAD3A7960EC6BB773D31FAD /* Foundation.framework in Frameworks */, - 170DBA9C1706F872A13EF10D /* ViroKit.xcframework in Frameworks */, - CC33DD44EE55FF00AA11BB22 /* Accelerate.framework in Frameworks */, - DD44EE55FF11AA22BB33CC00 /* MetalKit.framework in Frameworks */, + 4ECDB55704DC6991189F6758 /* Foundation.framework in Frameworks */, + 37A8FFBE20C92DDAC2134400 /* ViroKit.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 07BB710225A34B0FFB89B8CD /* iOS */ = { + 0BA00BBE4ADB428D1CB4A648 /* Renderer */ = { isa = PBXGroup; children = ( - 8A8B96073B0F338F063303CD /* Foundation.framework */, - DD44EE55FF00AA11BB22CC33 /* Accelerate.framework */, - EE55FF00AA11BB22CC33DD44 /* MetalKit.framework */, + 4CF19442C4E30C43FC324E11 /* Shaders.metal */, ); - name = iOS; + name = Renderer; + path = ../../ViroRenderer; sourceTree = ""; }; - 32787600C9AC3E4D65AA1FA0 /* App */ = { + 2FAEBE8608449320BDA9BA4E /* Frameworks */ = { isa = PBXGroup; children = ( - EA5E437D45A3FD76561F7905 /* ViroKitVisionOSTestApp.swift */, - 87F0D12727799D8549A697BB /* ContentView.swift */, - 838017A544325DD2C573C5C0 /* BridgingHeader.h */, - 7EB2A45A72A550C394486E9A /* Info.plist */, - BB22CC33DD44EE55FF00AA11 /* shiba.glb */, - FF22AA33BB44CC55DD66EE11 /* Soldier.glb */, - BB33CC44DD55EE66FF00AA22 /* ViroShadersSource.txt */, - EE33FF44AA55BB66CC77DD00 /* 360_space.jpg */, + 09FF92C372F8D731D4E166A7 /* ViroKit.xcframework */, ); - path = App; + name = Frameworks; sourceTree = ""; }; - 99DD88767324269696B2AEA4 /* Products */ = { + 83DCF74FC5D236BFD4C418D4 = { isa = PBXGroup; children = ( - 657CBA5513FCE67545DDBD75 /* ViroKitVisionOSTest.app */, + C74485B530829805D0C2FECF /* Products */, + E8296AEAC5461885599B517F /* Frameworks */, + CF3CD582196FED394CD1D783 /* App */, + B7309857229B702F585A77B4 /* VisionOS */, + 0BA00BBE4ADB428D1CB4A648 /* Renderer */, + 2FAEBE8608449320BDA9BA4E /* Frameworks */, ); - name = Products; sourceTree = ""; }; - A7B33025F073197BC47CB7A0 /* Frameworks */ = { + B7309857229B702F585A77B4 /* VisionOS */ = { isa = PBXGroup; children = ( - AF6CA2E5DBEA40C5563D2B44 /* ViroKit.xcframework */, + 172ADAE73BDCFB08E07AA330 /* VRORendererBridge.mm */, + E568A88357B0C4703ADB49E0 /* VROSharedSpaceBridge.mm */, + EF83DEBBF80477B516F6BB18 /* ViroImmersiveRenderer.swift */, + C4C869239B38DA8B4DBE6981 /* ViroImmersiveSpace.swift */, + 9E0343B9DF13ED7F476858B9 /* ViroImmersiveCoordinator.swift */, + 333F06DF489EF9A387E5D4C9 /* ViroSharedSpace.swift */, + 6B5250943694E5F813F8C159 /* VRORendererBridge.h */, + 05A802A93D9BE6D9030B5E9D /* VROSharedSpaceBridge.h */, ); - name = Frameworks; + name = VisionOS; + path = ../../../viro/ios/ViroReact/VisionOS; sourceTree = ""; }; - A89021F1941E0B125DF65727 /* Frameworks */ = { + C74485B530829805D0C2FECF /* Products */ = { isa = PBXGroup; children = ( - 07BB710225A34B0FFB89B8CD /* iOS */, + BC55F33CB214259A7ABE258B /* ViroKitVisionOSTest.app */, ); - name = Frameworks; + name = Products; sourceTree = ""; }; - CAAAC45131B95037BB018663 /* VisionOS */ = { + CF3CD582196FED394CD1D783 /* App */ = { isa = PBXGroup; children = ( - B2D8275BE5EBE1FCCCC9243D /* VRORendererBridge.mm */, - 83373494ECFFB003D8250D1C /* ViroImmersiveRenderer.swift */, - 220A2746ABFED3E4D24F80A3 /* ViroImmersiveSpace.swift */, - 80360FEA3D6EF6BC0017D88B /* ViroImmersiveCoordinator.swift */, - BD4A175078D88E89F9FA9977 /* VRORendererBridge.h */, + A97E80775E11EA68E813E42B /* ViroKitVisionOSTestApp.swift */, + 1384E6F46E1CA009E4CF9C0E /* ContentView.swift */, + 85BBE9BF03A472571062704F /* BridgingHeader.h */, + 147A65117E53C48B49FEE53A /* module.modulemap */, + 0F73C5844E1C2E4ED917AC07 /* Info.plist */, + 651818D080CD2CF7A0D19817 /* shiba.glb */, + E7FE2AABBCA56FBEDA805DA4 /* Soldier.glb */, + 12FB37DAACB265348A211265 /* DamagedHelmet.glb */, + E822F6D43D6EF9C8CB3041D2 /* 360_space.jpg */, + F9204880165ECC3C73490777 /* ViroShadersSource.txt */, ); - name = VisionOS; - path = ../../../viro/ios/ViroReact/VisionOS; + name = App; + path = App; sourceTree = ""; }; - CE82C304C023346AE454109F /* Renderer */ = { + E51217E83B1029CEA8088CD3 /* iOS */ = { isa = PBXGroup; children = ( - 39A5876AE5C9C8A7F0A0640C /* Shaders.metal */, + 0B49338AA8A683EB1A8BC216 /* Foundation.framework */, ); - name = Renderer; - path = ../../ViroRenderer; + name = iOS; sourceTree = ""; }; - FFCA809695C7701A7F1C1BB3 = { + E8296AEAC5461885599B517F /* Frameworks */ = { isa = PBXGroup; children = ( - 99DD88767324269696B2AEA4 /* Products */, - A89021F1941E0B125DF65727 /* Frameworks */, - 32787600C9AC3E4D65AA1FA0 /* App */, - CAAAC45131B95037BB018663 /* VisionOS */, - CE82C304C023346AE454109F /* Renderer */, - A7B33025F073197BC47CB7A0 /* Frameworks */, + E51217E83B1029CEA8088CD3 /* iOS */, ); + name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 54A09520D0AB7E6F6425B267 /* ViroKitVisionOSTest */ = { + 81A0C00D4066EFD70D335673 /* ViroKitVisionOSTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 440A39EBF846909105B536C1 /* Build configuration list for PBXNativeTarget "ViroKitVisionOSTest" */; + buildConfigurationList = EE7990D80F4D79EB800BD8E2 /* Build configuration list for PBXNativeTarget "ViroKitVisionOSTest" */; buildPhases = ( - 7E85A7C21D65D4C046E8ACC1 /* Sources */, - 4B25984028BE59690CC47ECC /* Frameworks */, - C01F005734A4344330F30F32 /* Resources */, - 664EF48F72E909D2BA20C617 /* Embed Frameworks */, + ABFFC0B2E02D131CDE152502 /* Sources */, + E6BB443E0799B3C58E1F8805 /* Frameworks */, + 243635189EB264C1A33E6EE9 /* Resources */, + C422AB8F57093A82996D6C4A /* Embed Frameworks */, ); buildRules = ( ); @@ -177,19 +183,19 @@ ); name = ViroKitVisionOSTest; productName = ViroKitVisionOSTest; - productReference = 657CBA5513FCE67545DDBD75 /* ViroKitVisionOSTest.app */; + productReference = BC55F33CB214259A7ABE258B /* ViroKitVisionOSTest.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - F3D934C556105D10524D77F9 /* Project object */ = { + A50DBF3687C130877BCA6F80 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1600; }; - buildConfigurationList = C372BE182A6F933531EAF62B /* Build configuration list for PBXProject "ViroKitVisionOSTest" */; + buildConfigurationList = 95147023BB1A1A2C8C034C21 /* Build configuration list for PBXProject "ViroKitVisionOSTest" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; @@ -197,49 +203,54 @@ en, Base, ); - mainGroup = FFCA809695C7701A7F1C1BB3; - productRefGroup = 99DD88767324269696B2AEA4 /* Products */; + mainGroup = 83DCF74FC5D236BFD4C418D4; + minimizedProjectReferenceProxies = 0; + preferredProjectObjectVersion = 100; + productRefGroup = C74485B530829805D0C2FECF /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 54A09520D0AB7E6F6425B267 /* ViroKitVisionOSTest */, + 81A0C00D4066EFD70D335673 /* ViroKitVisionOSTest */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - C01F005734A4344330F30F32 /* Resources */ = { + 243635189EB264C1A33E6EE9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA11BB22CC33DD44EE55FF00 /* shiba.glb in Resources */, - EE11FF22AA33BB44CC55DD66 /* Soldier.glb in Resources */, - AA22BB33CC44DD55EE66FF00 /* ViroShadersSource.txt in Resources */, - FF33AA44BB55CC66DD77EE00 /* 360_space.jpg in Resources */, + 85E7373405DE7164B5D05DBB /* shiba.glb in Resources */, + 46327BA743A2118A0F122A07 /* Soldier.glb in Resources */, + 92205ACCC6B87C32DD0F57F4 /* DamagedHelmet.glb in Resources */, + 6F95659E2B80FB6D2C3EB5F8 /* 360_space.jpg in Resources */, + 632FBEFFA03EC631E172414A /* ViroShadersSource.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 7E85A7C21D65D4C046E8ACC1 /* Sources */ = { + ABFFC0B2E02D131CDE152502 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1FED144A51634F8404989768 /* ViroKitVisionOSTestApp.swift in Sources */, - 54F8FAA93F6E49038D41216A /* ContentView.swift in Sources */, - 14493EB06DC2CF09C1ADCBA5 /* VRORendererBridge.mm in Sources */, - 3D28B8ABAB0E726934723067 /* ViroImmersiveRenderer.swift in Sources */, - 87A6671082280ABE2ED3122D /* ViroImmersiveSpace.swift in Sources */, - F40FB32E411CED8B4632BB82 /* ViroImmersiveCoordinator.swift in Sources */, - 09246380CF0B9F80FAA62036 /* Shaders.metal in Sources */, + DAB8C3312D52C62327C1A22E /* ViroKitVisionOSTestApp.swift in Sources */, + BF546BAFFE2A8363C13F7E23 /* ContentView.swift in Sources */, + 387D6379A4C0E85787BDAC3A /* VRORendererBridge.mm in Sources */, + 62EE1C58610699BCAF1AD5BB /* VROSharedSpaceBridge.mm in Sources */, + D19D0155F86CC348A8C46363 /* ViroImmersiveRenderer.swift in Sources */, + 1AB87DCCAE756077B52D0244 /* ViroImmersiveSpace.swift in Sources */, + 1D7C596954F8B884AA998CDF /* ViroImmersiveCoordinator.swift in Sources */, + 9FD16B6E22D64342C69F0970 /* ViroSharedSpace.swift in Sources */, + B0203B25FA68DBB4EC59E6EF /* Shaders.metal in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 5E1BCADAD6EF69483CFAFB57 /* Release */ = { + 111A556176C9988972EA5003 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -250,24 +261,33 @@ CLANG_ENABLE_OBJC_WEAK = NO; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = K9FMHQ779Z; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "VRO_METAL=1", "VRO_PLATFORM_VISION=1", ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "#{VISIONOS_SRC}", + ); INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = ""; - LD_RUNPATH_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_PREPROCESSOR_DEFINITIONS = "$(inherited) VRO_METAL=1"; + OTHER_LDFLAGS = ( "$(inherited)", - "@executable_path/Frameworks", + "-framework", + MetalKit, + "-framework", + Accelerate, ); - MTL_PREPROCESSOR_DEFINITIONS = "$(inherited) VRO_METAL=1"; PRODUCT_BUNDLE_IDENTIFIER = com.reactvision.ViroKitVisionOSTest; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = xros; SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_INCLUDE_PATHS = "$(inherited) $(SRCROOT)/App/ViroReactStub"; SWIFT_OBJC_BRIDGING_HEADER = App/BridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.9; @@ -277,7 +297,7 @@ }; name = Release; }; - 779DB6409C64F3D51377D08E /* Release */ = { + 9D1445DD88C9C494862473E1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -311,27 +331,80 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; + DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MTL_ENABLE_DEBUG_INFO = NO; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; - name = Release; + name = Debug; + }; + A2C003C791E03CC41A3D1398 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "VRO_METAL=1", + "VRO_PLATFORM_VISION=1", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "#{VISIONOS_SRC}", + ); + INFOPLIST_FILE = App/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = ""; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_PREPROCESSOR_DEFINITIONS = "$(inherited) VRO_METAL=1"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + MetalKit, + "-framework", + Accelerate, + ); + PRODUCT_BUNDLE_IDENTIFIER = com.reactvision.ViroKitVisionOSTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SUPPORTED_PLATFORMS = "xros xrsimulator"; + SWIFT_INCLUDE_PATHS = "$(inherited) $(SRCROOT)/App/ViroReactStub"; + SWIFT_OBJC_BRIDGING_HEADER = App/BridgingHeader.h; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.9; + TARGETED_DEVICE_FAMILY = 7; + XROS_DEPLOYMENT_TARGET = 26.0; + }; + name = Debug; }; - 99BE104CFC46789B3983670C /* Debug */ = { + B548379353EC4A202BAFB48A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -365,92 +438,48 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; - name = Debug; - }; - E5C8384EF5C2A448C5799BB7 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = K9FMHQ779Z; - ENABLE_BITCODE = NO; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "VRO_METAL=1", - "VRO_PLATFORM_VISION=1", - ); - INFOPLIST_FILE = App/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MTL_PREPROCESSOR_DEFINITIONS = "$(inherited) VRO_METAL=1"; - PRODUCT_BUNDLE_IDENTIFIER = com.reactvision.ViroKitVisionOSTest; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = xros; - SUPPORTED_PLATFORMS = "xros xrsimulator"; - SWIFT_OBJC_BRIDGING_HEADER = App/BridgingHeader.h; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.9; - TARGETED_DEVICE_FAMILY = 7; - XROS_DEPLOYMENT_TARGET = 26.0; - }; - name = Debug; + name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 440A39EBF846909105B536C1 /* Build configuration list for PBXNativeTarget "ViroKitVisionOSTest" */ = { + 95147023BB1A1A2C8C034C21 /* Build configuration list for PBXProject "ViroKitVisionOSTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 5E1BCADAD6EF69483CFAFB57 /* Release */, - E5C8384EF5C2A448C5799BB7 /* Debug */, + 9D1445DD88C9C494862473E1 /* Debug */, + B548379353EC4A202BAFB48A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C372BE182A6F933531EAF62B /* Build configuration list for PBXProject "ViroKitVisionOSTest" */ = { + EE7990D80F4D79EB800BD8E2 /* Build configuration list for PBXNativeTarget "ViroKitVisionOSTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 99BE104CFC46789B3983670C /* Debug */, - 779DB6409C64F3D51377D08E /* Release */, + 111A556176C9988972EA5003 /* Release */, + A2C003C791E03CC41A3D1398 /* Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = F3D934C556105D10524D77F9 /* Project object */; + rootObject = A50DBF3687C130877BCA6F80 /* Project object */; } diff --git a/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/xcshareddata/xcschemes/ViroKitVisionOSTest.xcscheme b/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/xcshareddata/xcschemes/ViroKitVisionOSTest.xcscheme index 87c734a41..737bcdcf9 100644 --- a/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/xcshareddata/xcschemes/ViroKitVisionOSTest.xcscheme +++ b/ios/ViroKitVisionOSTest/ViroKitVisionOSTest.xcodeproj/xcshareddata/xcschemes/ViroKitVisionOSTest.xcscheme @@ -14,7 +14,7 @@ buildForAnalyzing = "YES"> @@ -44,7 +44,7 @@ runnableDebuggingMode = "0"> diff --git a/ios/create_visionos_test_app.rb b/ios/create_visionos_test_app.rb index 7bb4df6e1..a2199e5fb 100755 --- a/ios/create_visionos_test_app.rb +++ b/ios/create_visionos_test_app.rb @@ -50,25 +50,34 @@ # ── App sources ─────────────────────────────────────────────────────────────── APP_SOURCES = %w[ViroKitVisionOSTestApp.swift ContentView.swift] -APP_HEADERS = %w[BridgingHeader.h] -APP_RESOURCES = %w[Info.plist] +APP_HEADERS = %w[BridgingHeader.h ViroReactStub/module.modulemap] +APP_FILES = %w[Info.plist] +# Bundled for the bridge's test scene (VRORendererBridge loads each GLB it finds in the +# main bundle) and for the runtime Metal library (ViroShadersSource.txt). +APP_RESOURCES = %w[shiba.glb Soldier.glb DamagedHelmet.glb 360_space.jpg ViroShadersSource.txt] APP_SOURCES.each do |name| ref = app_group.new_reference(File.join(APP_DIR, name)) target.add_file_references([ref]) end -APP_HEADERS.each { |n| app_group.new_reference(File.join(APP_DIR, n)) } -APP_RESOURCES.each { |n| app_group.new_reference(File.join(APP_DIR, n)) } +APP_HEADERS.each { |n| app_group.new_reference(File.join(APP_DIR, n)) } +APP_FILES.each { |n| app_group.new_reference(File.join(APP_DIR, n)) } +APP_RESOURCES.each do |name| + ref = app_group.new_reference(File.join(APP_DIR, name)) + target.resources_build_phase.add_file_reference(ref) +end # ── VisionOS sources (from viro repo) ───────────────────────────────────────── VISIONOS_COMPILE = %w[ VRORendererBridge.mm + VROSharedSpaceBridge.mm ViroImmersiveRenderer.swift ViroImmersiveSpace.swift ViroImmersiveCoordinator.swift + ViroSharedSpace.swift ] -VISIONOS_HEADERS = %w[VRORendererBridge.h] +VISIONOS_HEADERS = %w[VRORendererBridge.h VROSharedSpaceBridge.h] VISIONOS_COMPILE.each do |name| ref = visionos_group.new_reference(File.join(VISIONOS_SRC, name)) @@ -109,6 +118,12 @@ # Swift 'SWIFT_VERSION' => '5.9', 'SWIFT_OBJC_BRIDGING_HEADER' => 'App/BridgingHeader.h', + # ViroSharedSpace.swift does `import ViroReact`, the pod's module. Outside the pod a + # stub module of that name exposes the one header it needs (VROSharedSpaceBridge.h). + 'SWIFT_INCLUDE_PATHS' => ['$(inherited)', '$(SRCROOT)/App/ViroReactStub'], + 'HEADER_SEARCH_PATHS' => ['$(inherited)', '#{VISIONOS_SRC}'], + # ViroKit loads textures through MetalKit and converts images with Accelerate (vImage). + 'OTHER_LDFLAGS' => ['$(inherited)', '-framework', 'MetalKit', '-framework', 'Accelerate'], # C++ 'CLANG_CXX_LANGUAGE_STANDARD' => 'gnu++17', 'CLANG_CXX_LIBRARY' => 'libc++', @@ -165,5 +180,5 @@ puts " 1. open '#{PROJ_PATH}'" puts " 2. Choose destination: Vision Pro Simulator" puts " 3. Product → Run (⌘R)" -puts " 4. Tap 'Launch Immersive' — verify red box renders" +puts " 4. The immersive space opens by itself: the red box, shiba, Soldier and DamagedHelmet render" puts From 689a38966da0a4d868f37c1c85837169034bf722 Mon Sep 17 00:00:00 2001 From: Ernesto Mendoza Date: Mon, 9 Nov 2026 09:05:55 -0600 Subject: [PATCH 5/5] Changelog and release notes: 3.1.1 --- CHANGELOG.md | 13 +++++++++++++ RELEASE_NOTES.md | 46 +++++++++++----------------------------------- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a075475..c48a475fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG +## v3.1.1 + +### Added + +- **visionOS: PBR maps (`Shaders.metal`, `VROMaterialSubstrateMetal`, `VROGeometrySubstrateMetal`).** The Metal PBR path sampled only the base color, so a glTF model on visionOS lost its normal map, metallic-roughness, occlusion and emissive textures and drew flat (DamagedHelmet came out as a white silhouette). `pbr_lighting_fragment_maps` reads all five, by the same factors as the GL path, and the emissive map through its own texture transform. The normal map's tangent frame comes from screen-space derivatives, so geometry needs no tangent attribute. Maps bind at fragment texture indices 8-11 (`VROMaterialSubstrateMetal::getIndexedTextures`), past the shadow and IBL textures. +- **An occlusion map on its own UV set (`VROGLTFLoader`, `VROShaderFactory`, `VROMaterialVisual::setTexcoordSet`).** A glTF occlusionTexture on `TEXCOORD_1` (a lightmap) was sampled with the base color's coordinates. The second set is loaded as the new `VROGeometrySourceSemantic::Texcoord1` and read only by the occlusion map; GL binds it at attribute 6, the EdgeCrease slot no shader reads, since all 16 guaranteed slots were taken. Not on visionOS, whose Metal path reads one set: there such a map is left out. + +### Fixed + +- **Metals under flat ambient light (`VROShaderFactory`, `Shaders.metal`).** Without an environment map the PBR ambient term was `ambient × albedo × occlusion`, ignoring metalness, so metal surfaces lit like matte paint (a metal visor looked light grey). It is now scaled by `1 - metalness`, as three.js and Filament do: a metal has no diffuse response and nothing to reflect. +- **`normalTexture.scale` and `occlusionTexture.strength` apply (`VROShaderFactory`, `Shaders.metal`).** The loader read both into the visuals' intensity, which no shader used. The normal map's X and Y are scaled before normalizing, and occlusion is blended toward 1 by its strength. +- **Mirrored glTF nodes draw their faces (`VROGLTFLoader`).** A node whose world transform has a negative determinant (a negative scale) flips its triangles' winding, so back-face culling removed the faces that should show. The loader reverses the indices of a mirrored, unskinned mesh. + ## v3.1.0 — 8 November 2026 ### Added diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3d725dc2a..bf0346a7f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,46 +1,22 @@ # Release Notes -## v3.1.0 +## v3.1.1 -**Advanced VPS, a world mesh that persists, device depth in AR recordings, and a Meta Quest pass with hand occlusion.** The native build ships inside `@reactvision/react-viro` 3.1.0. It links Spatial 1.4.0, which adds continuous localisation against a scanned place and the VPS fuser. +**glTF models render as glTF defines them on every platform: metals look metallic without an environment map, visionOS draws a model's normal, metallic-roughness, occlusion and emissive maps, an occlusion map can sit on its own UV set, and mirrored nodes draw their faces.** The native build ships inside `@reactvision/react-viro` 3.1.1. -### Advanced VPS - -- **VPS map localisation in the AR session.** `VROARSession` gains map load, unload and localisation, implemented on ARKit and ARCore, and exposed on `ARScene` and through JNI. A loaded `.rvmap` is matched against camera frames while the session runs. -- **Server mode.** The engine hands frames out for the server to localise and feeds the answers into the same fuser, without loading a map (`rvStartVPSServerSession`, `rvReportVPSServerFix`, `rvStopVPSServerSession`, `VROARCamera::getImageTransform()`). -- **Every fix goes through Spatial's `VROVPSLocalizer`,** which keeps one smoothed map-to-world transform. `VROVPSAnchorNode` keeps content placed in map coordinates following it, and `VROARScene::setVPSMapMesh()` loads the place's mesh as occlusion and a kinematic physics body tagged `"world"`. -- **Recordings carry GPS and a camera-forward heading** on each pose, which ViroScan turns into the map's geo-registration. - -### World mesh +### Everywhere -- **Depth frames fuse into a persistent room (`VROTSDFVolume`, `VROARWorldMesh`)** on the depth-image and monocular paths, re-meshing only the blocks that changed, within a memory ceiling. Fusion pauses while tracking is limited, a depth drop-out keeps the room instead of falling back to plane polygons, and `resetAccumulation()` starts a new one. -- **The mesh reports its source** (`lidar`, `depth`, `monocular`, `plane`) and whether it is accumulated. -- **Android: the depth mesh is geometrically correct** and no longer depends on acquiring a CPU camera image. -- **Thread safety:** the Android snapshot no longer reads a mesh the render thread is replacing, a reset is not undone by a collision shape still being built, `voxelSize` and `maxMemoryMB` take effect mid-session, and iOS monocular depth is published whole with the pose it was inferred at. +- **Metals under flat ambient light:** without an environment map the ambient term now leaves out the share a metal reflects, so a metal visor is dark and reflective instead of light grey. +- **glTF factors:** `normalTexture.scale` and `occlusionTexture.strength` apply. +- **Occlusion on `TEXCOORD_1`** (a lightmap) samples its own UV set (GL; left out on visionOS). +- **Mirrored nodes** (a negative scale) draw their faces instead of losing them to back-face culling. -### Meta Quest +### visionOS -- **Input:** the left Menu button and the palm pinch report `MenuButton`, Y goes back as B does, a menu or system pinch never ends as a select, released hands release, and a hand and a controller on one side press once. `getSourceRay` / `getControllerRayAsync` report where a ray points, and `setInputFocusListener` / `setRoomMoveListener` report focus changes and recentres. -- **Rendering:** the right eye clears its depth buffer, each eye's framebuffer is made once and its depth discarded after drawing, the aim laser stays on top of panels, and switching AR scenes or closing one logs no errors. -- **Room planes** stay on the real surface across a recentre, are asked for at once, face the right way, and a desk is a table. -- **Hand occlusion in passthrough (optional, off by default):** the tracked hands are drawn into each eye's depth buffer before the scene, so the real hands show through virtual content in front of them. It uses the runtime's hand mesh, or capsules between the joints, on Quest 2, 3, 3S and Pro. -- **Stability:** videos no longer abort the app or leak JNI references, hit tests deliver their results without leaking, and `recenterTracking()` no longer races the render thread. +- **PBR maps:** normal, metallic-roughness, occlusion and emissive maps, which the Metal path skipped; a textured glTF model no longer draws flat. -### Everywhere +### Check when upgrading -- **AR recordings can carry the device's depth:** LiDAR scene depth on iOS and the ARCore Depth API on Android, as 16-bit millimetre PNGs with confidence, about 10 a second, when the session is set to capture depth. ViroScan fuses them into the scanned place's mesh, which is what makes blank walls reconstruct. -- **Physics runs at real time on every display,** in fixed 1/60 s substeps, instead of 1.5× fast at 90 Hz and 2× at 120 Hz. -- **ARCore reports the ambient light again,** and a paused AR session stays paused when an option or an image or object target changes. -- **visionOS:** spot light cones match the GL backends, and morph targets blend on the CPU. -- **Web:** vertex colours show, as on the native backends. -- **Co-location:** `VROColocationSession::deviceId()`, sent as `x-rv-device`, so the relay bills a device once. -- **A click goes to the node drawn on top,** and a click in open space reaches the controller's own delegates again. -- **Text sits where its vertical alignment puts it,** and a back-culled draw after a front-culled one culls back faces again. -- **GLB characters with one skin per mesh animate every part,** not only the first skin's. -- **glTF:** `KHR_texture_transform`, `KHR_node_visibility` and `KHR_animation_pointer` (node visibility, base colour and emissive factors, texture offset, rotation and scale), textures on `TEXCOORD_1` and later sets, and emissive textures. Opaque and masked materials ignore their textures' alpha, blended ones stop writing depth, skins animated through nodes between their joints move and are no longer culled by their bind-pose bounds, samplers without `interpolation` load, files with animation pointers no longer fail to load, emission shows on physically based materials, 8-bit index buffers draw, and skinned models draw on the web. -- **Android: a `ViroARCamera` no longer crashes the scene as it mounts.** -- **Video:** a paused iOS video stays paused and its late callbacks no longer reach a freed texture; Android play and pause keep their order. -- **iOS:** ViroKit targets iOS 15.1, and builds for the iOS Simulator (arm64 and x86_64) as well as the device. The copy into viro now makes `ViroKit.xcframework`, replacing only the slice just built. `ios/build_ios_simulator_libs.sh` adds the simulator builds of LinearMath, protobuf-lite and GVR Audio that ViroKit links. -- **Removed:** the macOS target, which no longer linked and nothing built, and dead code (an unused metrics client, the API key validator, the OpenCV headers, an old OpenXR loader). +- **A metallic model without a `lightingEnvironment` looks darker than in 3.1.0.** This is how glTF and three.js light it; give metallic models an environment map. See `CHANGELOG.md` for every change.