diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index d94c7b5972..16518d79b4 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -2496,7 +2496,7 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, return default_normal_height_tex; } - case Shader::STO_stage_selector_i: + case Shader::STO_stage_metallic_roughness_i: { const TextureAttrib *texattrib; if (_target_rs->get_attrib(texattrib)) { @@ -2505,7 +2505,8 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, TextureStage *stage = texattrib->get_on_stage(i); TextureStage::Mode mode = stage->get_mode(); - if (mode == TextureStage::M_selector) { + if (mode == TextureStage::M_metallic_roughness || + mode == TextureStage::M_occlusion_metallic_roughness) { if (si++ == spec._stage) { sampler = texattrib->get_on_sampler(stage); view += stage->get_tex_view_offset(); @@ -2538,6 +2539,28 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, } break; + case Shader::STO_stage_occlusion_i: + { + const TextureAttrib *texattrib; + if (_target_rs->get_attrib(texattrib)) { + int si = 0; + for (int i = 0; i < texattrib->get_num_on_stages(); ++i) { + TextureStage *stage = texattrib->get_on_stage(i); + TextureStage::Mode mode = stage->get_mode(); + + if (mode == TextureStage::M_occlusion || + mode == TextureStage::M_occlusion_metallic_roughness) { + if (si++ == spec._stage) { + sampler = texattrib->get_on_sampler(stage); + view += stage->get_tex_view_offset(); + return texattrib->get_on_texture(stage); + } + } + } + } + } + break; + default: nassertr(false, nullptr); break; diff --git a/panda/src/doc/eggSyntax.txt b/panda/src/doc/eggSyntax.txt index b81eb45c0e..c8225a66df 100644 --- a/panda/src/doc/eggSyntax.txt +++ b/panda/src/doc/eggSyntax.txt @@ -339,7 +339,10 @@ appear before they are referenced. *GLOW *GLOSS *HEIGHT - *SELECTOR + *EMISSION + *METALLIC_ROUGHNESS + *OCCLUSION + *OCCLUSION_METALLIC_ROUGHNESS The default environment type is MODULATE, which means the texture color is multiplied with the base polygon (or vertex) color. This diff --git a/panda/src/egg/eggTexture.cxx b/panda/src/egg/eggTexture.cxx index 6c78d57b3d..8b4e261455 100644 --- a/panda/src/egg/eggTexture.cxx +++ b/panda/src/egg/eggTexture.cxx @@ -561,11 +561,11 @@ affects_polygon_alpha() const { case ET_height: case ET_normal_gloss: case ET_emission: + case ET_occlusion: + case ET_metallic_roughness: + case ET_occlusion_metallic_roughness: return false; - case ET_selector: - return true; - case ET_unspecified: break; } @@ -883,8 +883,9 @@ string_env_type(const string &string) { } else if (cmp_nocase_uh(string, "height") == 0) { return ET_height; - } else if (cmp_nocase_uh(string, "selector") == 0) { - return ET_selector; + } else if (cmp_nocase_uh(string, "metallic_roughness") == 0 || + cmp_nocase_uh(string, "selector") == 0) { + return ET_metallic_roughness; } else if (cmp_nocase_uh(string, "normal_gloss") == 0) { return ET_normal_gloss; @@ -892,6 +893,12 @@ string_env_type(const string &string) { } else if (cmp_nocase_uh(string, "emission") == 0) { return ET_emission; + } else if (cmp_nocase_uh(string, "occlusion") == 0) { + return ET_occlusion; + + } else if (cmp_nocase_uh(string, "occlusion_metallic_roughness") == 0) { + return ET_occlusion_metallic_roughness; + } else { return ET_unspecified; } @@ -1321,14 +1328,20 @@ ostream &operator << (ostream &out, EggTexture::EnvType type) { case EggTexture::ET_height: return out << "height"; - case EggTexture::ET_selector: - return out << "selector"; + case EggTexture::ET_metallic_roughness: + return out << "metallic_roughness"; case EggTexture::ET_normal_gloss: return out << "normal_gloss"; case EggTexture::ET_emission: return out << "emission"; + + case EggTexture::ET_occlusion: + return out << "occlusion"; + + case EggTexture::ET_occlusion_metallic_roughness: + return out << "occlusion_metallic_roughness"; } nassertr(false, out); diff --git a/panda/src/egg/eggTexture.h b/panda/src/egg/eggTexture.h index e67cc2cdb3..8a8435c9e9 100644 --- a/panda/src/egg/eggTexture.h +++ b/panda/src/egg/eggTexture.h @@ -106,9 +106,12 @@ PUBLISHED: ET_glow, ET_gloss, ET_height, - ET_selector, + ET_metallic_roughness, ET_normal_gloss, ET_emission, + ET_occlusion, + ET_occlusion_metallic_roughness, + ET_selector = ET_metallic_roughness, }; enum CombineMode { CM_unspecified, diff --git a/panda/src/egg2pg/eggLoader.cxx b/panda/src/egg2pg/eggLoader.cxx index e0dca70801..2b5a037cab 100644 --- a/panda/src/egg2pg/eggLoader.cxx +++ b/panda/src/egg2pg/eggLoader.cxx @@ -1579,8 +1579,8 @@ make_texture_stage(const EggTexture *egg_tex) { stage->set_mode(TextureStage::M_height); break; - case EggTexture::ET_selector: - stage->set_mode(TextureStage::M_selector); + case EggTexture::ET_metallic_roughness: + stage->set_mode(TextureStage::M_metallic_roughness); break; case EggTexture::ET_normal_gloss: @@ -1591,6 +1591,14 @@ make_texture_stage(const EggTexture *egg_tex) { stage->set_mode(TextureStage::M_emission); break; + case EggTexture::ET_occlusion: + stage->set_mode(TextureStage::M_occlusion); + break; + + case EggTexture::ET_occlusion_metallic_roughness: + stage->set_mode(TextureStage::M_occlusion_metallic_roughness); + break; + case EggTexture::ET_unspecified: break; } diff --git a/panda/src/egg2pg/eggSaver.cxx b/panda/src/egg2pg/eggSaver.cxx index ebfaf3d750..141a09549d 100644 --- a/panda/src/egg2pg/eggSaver.cxx +++ b/panda/src/egg2pg/eggSaver.cxx @@ -852,8 +852,8 @@ convert_primitive(const GeomVertexData *vertex_data, case TextureStage::M_height: egg_tex->set_env_type(EggTexture::ET_height); break; - case TextureStage::M_selector: - egg_tex->set_env_type(EggTexture::ET_selector); + case TextureStage::M_metallic_roughness: + egg_tex->set_env_type(EggTexture::ET_metallic_roughness); break; case TextureStage::M_normal_gloss: egg_tex->set_env_type(EggTexture::ET_normal_gloss); @@ -861,6 +861,12 @@ convert_primitive(const GeomVertexData *vertex_data, case TextureStage::M_emission: egg_tex->set_env_type(EggTexture::ET_emission); break; + case TextureStage::M_occlusion: + egg_tex->set_env_type(EggTexture::ET_occlusion); + break; + case TextureStage::M_occlusion_metallic_roughness: + egg_tex->set_env_type(EggTexture::ET_occlusion_metallic_roughness); + break; default: break; } diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index 9826fad4bb..37140ce9f0 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -1043,8 +1043,9 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) { else if (noprefix.compare(7, string::npos, "Height") == 0) { bind._part = Shader::STO_stage_height_i; } - else if (noprefix.compare(7, string::npos, "Selector") == 0) { - bind._part = Shader::STO_stage_selector_i; + else if (noprefix.compare(7, string::npos, "MetallicRoughness") == 0 || + noprefix.compare(7, string::npos, "Selector") == 0) { + bind._part = Shader::STO_stage_metallic_roughness_i; } else if (noprefix.compare(7, string::npos, "Gloss") == 0) { bind._part = Shader::STO_stage_gloss_i; @@ -1052,6 +1053,9 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) { else if (noprefix.compare(7, string::npos, "Emission") == 0) { bind._part = Shader::STO_stage_emission_i; } + else if (noprefix.compare(7, string::npos, "Occlusion") == 0) { + bind._part = Shader::STO_stage_occlusion_i; + } else { GLCAT.error() << "Unrecognized shader input name: p3d_" << noprefix << "\n"; diff --git a/panda/src/gobj/shader.h b/panda/src/gobj/shader.h index 4bd548d331..88021d2403 100644 --- a/panda/src/gobj/shader.h +++ b/panda/src/gobj/shader.h @@ -232,9 +232,10 @@ public: STO_stage_add_i, STO_stage_normal_i, STO_stage_height_i, - STO_stage_selector_i, + STO_stage_metallic_roughness_i, STO_stage_gloss_i, STO_stage_emission_i, + STO_stage_occlusion_i, }; enum ShaderArgClass { diff --git a/panda/src/gobj/textureStage.cxx b/panda/src/gobj/textureStage.cxx index 3540deb92d..8292083773 100644 --- a/panda/src/gobj/textureStage.cxx +++ b/panda/src/gobj/textureStage.cxx @@ -519,14 +519,20 @@ operator << (ostream &out, TextureStage::Mode mode) { case TextureStage::M_height: return out << "height"; - case TextureStage::M_selector: - return out << "selector"; + case TextureStage::M_metallic_roughness: + return out << "metallic_roughness"; case TextureStage::M_normal_gloss: return out << "normal_gloss"; case TextureStage::M_emission: return out << "emission"; + + case TextureStage::M_occlusion: + return out << "occlusion"; + + case TextureStage::M_occlusion_metallic_roughness: + return out << "occlusion_metallic_roughness"; } return out << "**invalid Mode(" << (int)mode << ")**"; diff --git a/panda/src/gobj/textureStage.h b/panda/src/gobj/textureStage.h index 10a9bc5251..ff5bcb82be 100644 --- a/panda/src/gobj/textureStage.h +++ b/panda/src/gobj/textureStage.h @@ -61,10 +61,14 @@ PUBLISHED: M_glow, // Rarely used: modulate_glow is more efficient. M_gloss, // Rarely used: modulate_gloss is more efficient. M_height, // Rarely used: normal_height is more efficient. - M_selector, + M_metallic_roughness, // metalness in B, roughness in G M_normal_gloss, M_emission, + M_occlusion, // In red channel + M_occlusion_metallic_roughness, + + M_selector = M_metallic_roughness, }; enum CombineMode { diff --git a/panda/src/grutil/multitexReducer.cxx b/panda/src/grutil/multitexReducer.cxx index fd94e1d173..f2b3107051 100644 --- a/panda/src/grutil/multitexReducer.cxx +++ b/panda/src/grutil/multitexReducer.cxx @@ -712,9 +712,11 @@ make_texture_layer(const NodePath &render, case TextureStage::M_glow: case TextureStage::M_gloss: case TextureStage::M_height: - case TextureStage::M_selector: + case TextureStage::M_metallic_roughness: case TextureStage::M_normal_gloss: case TextureStage::M_emission: + case TextureStage::M_occlusion: + case TextureStage::M_occlusion_metallic_roughness: // Don't know what to do with these funny modes. We should probably raise // an exception or something. Fall through for now. diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index 5a8a17c9df..dd5e9f4c11 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -491,6 +491,10 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { case TextureStage::M_emission: info._flags = ShaderKey::TF_map_emission; break; + case TextureStage::M_occlusion: + case TextureStage::M_occlusion_metallic_roughness: + info._flags = ShaderKey::TF_map_occlusion; + break; default: break; } @@ -557,9 +561,13 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { // Decide whether to separate ambient and diffuse calculations. if (have_ambient) { - if (key._material_flags & Material::F_ambient) { + if (key._texture_flags & ShaderKey::TF_map_occlusion) { key._have_separate_ambient = true; - } else { + } + else if (key._material_flags & Material::F_ambient) { + key._have_separate_ambient = true; + } + else { if (key._material_flags & Material::F_diffuse) { key._have_separate_ambient = true; } else { @@ -1599,6 +1607,13 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) { text << "\t result *= saturate(2 * (tex" << map_index_glow << ".a - 0.5));\n"; } if (key._have_separate_ambient) { + // Apply occlusion textures. + for (size_t i = 0; i < key._textures.size(); ++i) { + ShaderKey::TextureInfo &tex = key._textures[i]; + if (tex._flags & ShaderKey::TF_map_occlusion) { + text << "\t tot_ambient *= tex" << i << ".r;\n"; + } + } if (key._material_flags & Material::F_ambient) { text << "\t result += tot_ambient * attr_material[0];\n"; } else if (key._color_type == ColorAttrib::T_vertex) { diff --git a/panda/src/pgraphnodes/shaderGenerator.h b/panda/src/pgraphnodes/shaderGenerator.h index f23fe3b315..fe574707f1 100644 --- a/panda/src/pgraphnodes/shaderGenerator.h +++ b/panda/src/pgraphnodes/shaderGenerator.h @@ -111,6 +111,7 @@ protected: TF_map_glow = 0x080, TF_map_gloss = 0x100, TF_map_emission = 0x001000000, + TF_map_occlusion = 0x002000000, TF_uses_color = 0x200, TF_uses_primary_color = 0x400, TF_uses_last_saved_result = 0x800, diff --git a/pandatool/src/assimp/assimpLoader.cxx b/pandatool/src/assimp/assimpLoader.cxx index 80b94a7ca3..592520df0e 100644 --- a/pandatool/src/assimp/assimpLoader.cxx +++ b/pandatool/src/assimp/assimpLoader.cxx @@ -58,6 +58,10 @@ #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 #endif +#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE +#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE aiTextureType_UNKNOWN, 0 +#endif + #ifndef AI_MATKEY_GLTF_ALPHAMODE #define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0 #endif @@ -325,11 +329,13 @@ load_texture(size_t index) { /** * Converts an aiMaterial into a RenderState. + * The dummy argument exists so we can pass a MATKEY macro into this function + * instead of just a texture type. */ void AssimpLoader:: -load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, - TextureStage::Mode mode, CPT(TextureAttrib) &tattr, - CPT(TexMatrixAttrib) &tmattr) { +load_texture_stage(const aiMaterial &mat, TextureStage::Mode mode, + CPT(TextureAttrib) &tattr, CPT(TexMatrixAttrib) &tmattr, + const aiTextureType &ttype, unsigned int dummy) { aiString path; aiTextureMapping mapping; unsigned int uvindex; @@ -596,20 +602,33 @@ load_material(size_t index) { // And let's not forget the textures! CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make()); CPT(TexMatrixAttrib) tmattr; - load_texture_stage(mat, aiTextureType_DIFFUSE, TextureStage::M_modulate, tattr, tmattr); + load_texture_stage(mat, TextureStage::M_modulate, tattr, tmattr, aiTextureType_DIFFUSE); - // Check for an ORM map, from the glTF/OBJ importer. glTF also puts it in the - // LIGHTMAP slot, despite only having the lightmap in the red channel, so we - // have to ignore it. - if (mat.GetTextureCount(aiTextureType_UNKNOWN) > 0) { - load_texture_stage(mat, aiTextureType_UNKNOWN, TextureStage::M_selector, tattr, tmattr); + // Check for an ORM map, from the glTF/OBJ importer. glTF also erroneously + // puts an occlusion texture in the LIGHTMAP slot. + aiString roughness_path, occlusion_path; + aiTextureMapping roughness_mapping, occlusion_mapping; + unsigned int roughness_uv, occlusion_uv; + if (AI_SUCCESS == mat.GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &roughness_path, &roughness_mapping, &roughness_uv)) { + if (AI_SUCCESS == mat.GetTexture(aiTextureType_LIGHTMAP, 0, &occlusion_path, &occlusion_mapping, &occlusion_uv)) { + if (roughness_path == occlusion_path && + roughness_mapping == occlusion_mapping && + roughness_uv == occlusion_uv) { + load_texture_stage(mat, TextureStage::M_occlusion_metallic_roughness, tattr, tmattr, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + } else { + load_texture_stage(mat, TextureStage::M_metallic_roughness, tattr, tmattr, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + load_texture_stage(mat, TextureStage::M_occlusion, tattr, tmattr, aiTextureType_LIGHTMAP); + } + } else { + load_texture_stage(mat, TextureStage::M_metallic_roughness, tattr, tmattr, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + } } else { - load_texture_stage(mat, aiTextureType_LIGHTMAP, TextureStage::M_modulate, tattr, tmattr); + load_texture_stage(mat, TextureStage::M_modulate, tattr, tmattr, aiTextureType_LIGHTMAP); } - load_texture_stage(mat, aiTextureType_NORMALS, TextureStage::M_normal, tattr, tmattr); - load_texture_stage(mat, aiTextureType_EMISSIVE, TextureStage::M_emission, tattr, tmattr); - load_texture_stage(mat, aiTextureType_HEIGHT, TextureStage::M_height, tattr, tmattr); + load_texture_stage(mat, TextureStage::M_normal, tattr, tmattr, aiTextureType_NORMALS); + load_texture_stage(mat, TextureStage::M_emission, tattr, tmattr, aiTextureType_EMISSIVE); + load_texture_stage(mat, TextureStage::M_height, tattr, tmattr, aiTextureType_HEIGHT); if (tattr->get_num_on_stages() > 0) { state = state->add_attrib(tattr); } diff --git a/pandatool/src/assimp/assimpLoader.h b/pandatool/src/assimp/assimpLoader.h index e323cb1e94..e36e146a23 100644 --- a/pandatool/src/assimp/assimpLoader.h +++ b/pandatool/src/assimp/assimpLoader.h @@ -78,9 +78,9 @@ private: const aiNode *find_node(const aiNode &root, const aiString &name); void load_texture(size_t index); - void load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, - TextureStage::Mode mode, CPT(TextureAttrib) &tattr, - CPT(TexMatrixAttrib) &tmattr); + void load_texture_stage(const aiMaterial &mat, TextureStage::Mode mode, + CPT(TextureAttrib) &tattr, CPT(TexMatrixAttrib) &tmattr, + const aiTextureType &ttype, unsigned int dummy=0); void load_material(size_t index); void create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node); void create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node); diff --git a/tests/display/test_color_buffer.py b/tests/display/test_color_buffer.py index 1450981f37..e2a43991ad 100644 --- a/tests/display/test_color_buffer.py +++ b/tests/display/test_color_buffer.py @@ -367,3 +367,31 @@ def test_color_transparency_no_light(color_region, shader_attrib): ) result = render_color_pixel(color_region, state) assert result.x == pytest.approx(1.0, 0.1) + + +def test_texture_occlusion(color_region): + shader_attrib = core.ShaderAttrib.make_default().set_shader_auto(True) + + tex = core.Texture("occlusion") + tex.set_clear_color((0.5, 1.0, 1.0, 1.0)) + stage = core.TextureStage("occlusion") + stage.set_mode(core.TextureStage.M_occlusion) + texture_attrib = core.TextureAttrib.make().add_on_stage(stage, tex) + + mat = core.Material() + mat.diffuse = (0, 1, 0, 1) + mat.ambient = (0, 0, 1, 1) + material_attrib = core.MaterialAttrib.make(mat) + + alight = core.AmbientLight("ambient") + alight.set_color((0, 0, 1, 1)) + light_attrib = core.LightAttrib.make().add_on_light(core.NodePath(alight)) + + state = core.RenderState.make( + shader_attrib, + material_attrib, + texture_attrib, + light_attrib + ) + result = render_color_pixel(color_region, state) + assert result.z == pytest.approx(0.5, 0.05)