Add support for (occlusion-)metallic-roughness textures
Existing "selector" mode (which was unused and has been used to implement ORM textures has been renamed to metallic-roughness, and new modes have been added for occlusion and occlusion-metallic-roughness. New GLSL input names have been added. The .egg format has been extended. The Assimp loader has been updated. The shader generator now handles occlusion maps, and a unit test for that is added.
This commit is contained in:
parent
bd1105d545
commit
04e5cbe706
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 << ")**";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue