// // CustomMaterial.cpp // building // // Created by yanqian on 2023/5/8. // #include "CustomMaterial.hpp" NS_CC_BEGIN // Helpers declaration static const char* getOptionalString(Properties* properties, const char* key, const char* defaultValue); static bool isValidUniform(const char* name); CustomMaterial::CustomMaterial() { _time = 0; _paramFloat.clear(); _paramVec2.clear(); _paramVec3.clear(); _paramVec4.clear(); _paramMat4.clear(); _paramString.clear(); } CustomMaterial::~CustomMaterial() { _paramFloat.clear(); _paramVec2.clear(); _paramVec3.clear(); _paramVec4.clear(); _paramMat4.clear(); _paramString.clear(); } CustomMaterial* CustomMaterial::createWithFilename(const std::string& filepath) { std::string validfilename = FileUtils::getInstance()->fullPathForFilename(filepath); if (validfilename.size() > 0) { CustomMaterial* mat = new (std::nothrow) CustomMaterial(); if (mat) { mat->setRelativePath(filepath); // 设置材质文件的相对路径 if (mat->initWithFile(validfilename)) { mat->autorelease(); return mat; } } } return nullptr; } bool CustomMaterial::initWithFile(const std::string& validfilename) { Properties* properties = Properties::createNonRefCounted(validfilename); // 解析材质属性 parseProperties((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace()); CC_SAFE_DELETE(properties); return true; } bool CustomMaterial::parseProperties(Properties* materialProperties) { setName(materialProperties->getId()); Properties* space = materialProperties->getNextNamespace(); while (space) { // 遍历属性对象中的命名空间 const char* name = space->getNamespace(); if (strcmp(name, "technique") == 0) { parseTechnique(space); } else if (strcmp(name, "renderState") == 0) { parseRenderState(this, space); } space = materialProperties->getNextNamespace(); } return true; } bool CustomMaterial::parseTechnique(Properties* techniqueProperties) { Technique* technique = Technique::create(this); _techniques.pushBack(technique); if (!_currentTechnique) { _currentTechnique = technique; } technique->setName(techniqueProperties->getId()); // 设置材质名称 Properties* space = techniqueProperties->getNextNamespace(); while (space) { // 遍历technique属性对象中的命名空间 const char* name = space->getNamespace(); if (strcmp(name, "pass") == 0) { parsePass(technique, space); } else if (strcmp(name, "renderState") == 0) { parseRenderState(this, space); } space = techniqueProperties->getNextNamespace(); } return true; } bool CustomMaterial::parsePass(Technique* technique, Properties* passProperties) { Pass* pass = Pass::create(technique); if (pass == nullptr) { return false; } technique->addPass(pass); Properties* space = passProperties->getNextNamespace(); while (space) { // 遍历pass属性对象中的命名空间 const char* name = space->getNamespace(); if (strcmp(name, "shader") == 0) { parseShader(pass, space); } else if (strcmp(name, "renderState") == 0) { parseRenderState(pass, space); } else if (strcmp(name, "editorUniforms") == 0) { _parseEditorUniform(space); } else if (strcmp(name, "uniforms") == 0) { _parseUniformParams(pass->getGLProgramState(), space); } else { CCASSERT(false, "Invalid namespace"); return false; } space = passProperties->getNextNamespace(); } return true; } bool CustomMaterial::parseShader(Pass* pass, Properties* shaderProperties) { // vertexShader const char* vertShader = getOptionalString(shaderProperties, "vertexShader", nullptr); // fragmentShader const char* fragShader = getOptionalString(shaderProperties, "fragmentShader", nullptr); // compileTimeDefines const char* compileTimeDefines = getOptionalString(shaderProperties, "defines", ""); if (vertShader && fragShader) { auto glProgramState = GLProgramState::getOrCreateWithShaders(_relativePath + vertShader, _relativePath + fragShader, compileTimeDefines); pass->setGLProgramState(glProgramState); // Parse uniforms only if the GLProgramState was created auto property = shaderProperties->getNextProperty(); while (property) { if (isValidUniform(property)) { parseUniform(glProgramState, shaderProperties, property); } property = shaderProperties->getNextProperty(); } auto space = shaderProperties->getNextNamespace(); while (space) { const char* name = space->getNamespace(); if (strcmp(name, "sampler") == 0) { parseSampler(glProgramState, space); } space = shaderProperties->getNextNamespace(); } } return true; } bool CustomMaterial::parseRenderState(RenderState* renderState, Properties* properties) { auto state = renderState->getStateBlock(); auto property = properties->getNextProperty(); while (property) { // Parse uniforms only if the GLProgramState was created // Render state only can have "strings" or numbers as values. No new namespaces state->setState(property, properties->getString(property)); std::string value = properties->getString(property); if (std::string(property).compare("blend") == 0) { if (value.compare("true") == 0) { _useBlendFunc = true; } } else if (std::string(property).compare("blendSrc") == 0) { _blendFunc.src = _parseBlend(value); } else if (std::string(property).compare("blendDst") == 0) { _blendFunc.dst = _parseBlend(value); } property = properties->getNextProperty(); } return true; } int CustomMaterial::_parseBlend(const std::string& value) { std::string upper(value); std::transform(upper.begin(), upper.end(), upper.begin(), (int(*)(int))toupper); if (upper == "ZERO") return GL_ZERO; else if (upper == "ONE") return GL_ONE; else if (upper == "SRC_COLOR") return GL_SRC_COLOR; else if (upper == "ONE_MINUS_SRC_COLOR") return GL_ONE_MINUS_SRC_COLOR; else if (upper == "DST_COLOR") return GL_DST_COLOR; else if (upper == "ONE_MINUS_DST_COLOR") return GL_ONE_MINUS_DST_COLOR; else if (upper == "SRC_ALPHA") return GL_SRC_ALPHA; else if (upper == "ONE_MINUS_SRC_ALPHA") return GL_ONE_MINUS_SRC_ALPHA; else if (upper == "DST_ALPHA") return GL_DST_ALPHA; else if (upper == "ONE_MINUS_DST_ALPHA") return GL_ONE_MINUS_DST_ALPHA; else if (upper == "SRC_ALPHA_SATURATE") return GL_SRC_ALPHA_SATURATE; else { CCLOG("Unsupported blend value (%s). (Will default to BLEND_ONE if errors are treated as warnings)", value.c_str()); return RenderState::BLEND_ONE; } } bool CustomMaterial::parseSampler(GLProgramState* glProgramState, Properties* samplerProperties) { CCASSERT(samplerProperties->getId(), "Sampler must have an id. The id is the uniform name"); if (glProgramState == nullptr || samplerProperties == nullptr) { return false; } // required bool canBeEdit = samplerProperties->getBool("editorTexture"); const char* filename = samplerProperties->getString("path"); filename = (filename == nullptr) ? "" : filename; std::string filePath = (canBeEdit == false) ? _relativePath + filename : filename; Texture2D* texture = nullptr; Vec2 texRectOffset, texRectSize; // 先从图集里找 SpriteFrame *spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(filename); if (spriteFrame != nullptr) { texture = spriteFrame->getTexture(); // 计算纹理的偏移和大小 Rect rect = spriteFrame->getRectInPixels(); Size atlasSize = spriteFrame->getTexture()->getContentSizeInPixels(); texRectOffset = Vec2(rect.origin.x / atlasSize.width, rect.origin.y / atlasSize.height); texRectSize = Vec2(rect.size.width / atlasSize.width, rect.size.height / atlasSize.height); } else { texture = Director::getInstance()->getTextureCache()->addImage(filePath); texRectOffset = Vec2(0, 0); texRectSize = Vec2(1, 1); } Texture2D::TexParams texParams; // mipmap bool usemipmap = false; const char* mipmap = getOptionalString(samplerProperties, "mipmap", "false"); if (mipmap && strcasecmp(mipmap, "true")==0) { usemipmap = true; } // valid options: REPEAT, CLAMP const char* wrapS = getOptionalString(samplerProperties, "wrapS", "CLAMP_TO_EDGE"); if (strcasecmp(wrapS, "REPEAT")==0) texParams.wrapS = GL_REPEAT; else if(strcasecmp(wrapS, "CLAMP_TO_EDGE")==0) texParams.wrapS = GL_CLAMP_TO_EDGE; else CCLOG("Invalid wrapS: %s", wrapS); // valid options: REPEAT, CLAMP const char* wrapT = getOptionalString(samplerProperties, "wrapT", "CLAMP_TO_EDGE"); if (strcasecmp(wrapT, "REPEAT")==0) texParams.wrapT = GL_REPEAT; else if(strcasecmp(wrapT, "CLAMP_TO_EDGE")==0) texParams.wrapT = GL_CLAMP_TO_EDGE; else CCLOG("Invalid wrapT: %s", wrapT); // valid options: NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, LINEAR_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_LINEAR const char* minFilter = getOptionalString(samplerProperties, "minFilter", usemipmap ? "LINEAR_MIPMAP_NEAREST" : "LINEAR"); if (strcasecmp(minFilter, "NEAREST")==0) texParams.minFilter = GL_NEAREST; else if(strcasecmp(minFilter, "LINEAR")==0) texParams.minFilter = GL_LINEAR; else if(strcasecmp(minFilter, "NEAREST_MIPMAP_NEAREST")==0) texParams.minFilter = GL_NEAREST_MIPMAP_NEAREST; else if(strcasecmp(minFilter, "LINEAR_MIPMAP_NEAREST")==0) texParams.minFilter = GL_LINEAR_MIPMAP_NEAREST; else if(strcasecmp(minFilter, "NEAREST_MIPMAP_LINEAR")==0) texParams.minFilter = GL_NEAREST_MIPMAP_LINEAR; else if(strcasecmp(minFilter, "LINEAR_MIPMAP_LINEAR")==0) texParams.minFilter = GL_LINEAR_MIPMAP_LINEAR; else CCLOG("Invalid minFilter: %s", minFilter); // valid options: NEAREST, LINEAR const char* magFilter = getOptionalString(samplerProperties, "magFilter", "LINEAR"); if (strcasecmp(magFilter, "NEAREST")==0) texParams.magFilter = GL_NEAREST; else if(strcasecmp(magFilter, "LINEAR")==0) texParams.magFilter = GL_LINEAR; else CCLOG("Invalid magFilter: %s", magFilter); if (texture) { if (usemipmap == true) { texture->generateMipmap(); } texture->setTexParameters(texParams); glProgramState->setUniformTexture(samplerProperties->getId(), texture); glProgramState->setUniformVec2("u_texRectOffset", texRectOffset); glProgramState->setUniformVec2("u_texRectSize", texRectSize); } if (canBeEdit) { _paramTexture.push_back(UniformParamTexture(samplerProperties->getId(), texParams)); } return true; } bool CustomMaterial::_parseEditorUniform(Properties* paramsProperties) { const char* property = paramsProperties->getNextProperty(); while (property) { _editorUniforms[property] = paramsProperties->getBool(property); property = paramsProperties->getNextProperty(); } return true; } bool CustomMaterial::_parseUniformParams(GLProgramState* programState, Properties* paramsProperties) { if (programState == nullptr || paramsProperties == nullptr) { return false; } const char* property = paramsProperties->getNextProperty(); while (property) { // 遍历所有属性变量 Properties::Type type = paramsProperties->getType(property); switch (type) { case Properties::Type::NUMBER: { float f = paramsProperties->getFloat(property); programState->setUniformFloat(property, f); if (_editorUniforms[property] == true) { _paramFloat.push_back(UniformParamFloat(property, f)); } break; } case Properties::Type::VECTOR2: { Vec2 v2; paramsProperties->getVec2(property, &v2); programState->setUniformVec2(property, v2); if (_editorUniforms[property] == true) { _paramVec2.push_back(UniformParamVec2(property, v2)); } break; } case Properties::Type::VECTOR3: { Vec3 v3; paramsProperties->getVec3(property, &v3); programState->setUniformVec3(property, v3); if (_editorUniforms[property] == true) { _paramVec3.push_back(UniformParamVec3(property, v3)); } break; } case Properties::Type::VECTOR4: { Vec4 v4; paramsProperties->getVec4(property, &v4); programState->setUniformVec4(property, v4); if (_editorUniforms[property] == true) { _paramVec4.push_back(UniformParamVec4(property, v4)); } break; } case Properties::Type::MATRIX: { Mat4 m4; paramsProperties->getMat4(property, &m4); programState->setUniformMat4(property, m4); if (_editorUniforms[property] == true) { _paramMat4.push_back(UniformParamMat4(property, m4)); } break; } case Properties::Type::STRING: { programState->setParameterAutoBinding(property, paramsProperties->getString()); if (_editorUniforms[property] == true) { _paramString.push_back(UniformParamString(property, paramsProperties->getString())); } break; } default: CCASSERT(false, "未知uniform变量类型"); break; } property = paramsProperties->getNextProperty(); } return true; } void CustomMaterial::updateUniformTime(float delta) { // 累加time _time += delta; if (_time >= 10000) { _time = 0; } // 对每个pass中包含u_time的uniform进行参数更新 for (auto technique : getTechniques()) { for (auto pass : technique->getPasses()) { auto glProgramState = pass->getGLProgramState(); if (glProgramState != nullptr && glProgramState->getGLProgram()->getUniformLocation("u_time") != -1) { glProgramState->setUniformFloat("u_time", _time); } } } } const std::vector CustomMaterial::getParamsFloat() { return _paramFloat; } const std::vector CustomMaterial::getParamsVec2() { return _paramVec2; } const std::vector CustomMaterial::getParamsVec3() { return _paramVec3; } const std::vector CustomMaterial::getParamsVec4() { return _paramVec4; } const std::vector CustomMaterial::getParamsMat4() { return _paramMat4; } const std::vector CustomMaterial::getParamsString() { return _paramString; } void CustomMaterial::setParamFloat(std::string name, float value) { for (auto ¶m : _paramFloat) { if (param.name == name) { param.value = value; } } GLProgramState* programState = _getGLProgramState(); if (programState) { programState->setUniformFloat(name, value); } } void CustomMaterial::setParamsVec2(std::string name, Vec2 value) { for (auto ¶m : _paramVec2) { if (param.name == name) { param.value = value; } } GLProgramState* programState = _getGLProgramState(); if (programState) { programState->setUniformVec2(name, value); } } void CustomMaterial::setParamsVec3(std::string name, Vec3 value) { for (auto ¶m : _paramVec3) { if (param.name == name) { param.value = value; } } GLProgramState* programState = _getGLProgramState(); if (programState) { programState->setUniformVec3(name, value); } } void CustomMaterial::setParamsVec4(std::string name, Vec4 value) { for (auto ¶m : _paramVec4) { if (param.name == name) { param.value = value; } } GLProgramState* programState = _getGLProgramState(); if (programState) { programState->setUniformVec4(name, value); } } void CustomMaterial::setParamsMat4(std::string name, Mat4 value) { for (auto ¶m : _paramMat4) { if (param.name == name) { param.value = value; } } GLProgramState* programState = _getGLProgramState(); if (programState) { programState->setUniformMat4(name, value); } } void CustomMaterial::setParamsString(std::string name, std::string value) { for (auto ¶m : _paramString) { if (param.name == name) { param.value = value; } } GLProgramState* programState = _getGLProgramState(); if (programState) { programState->setParameterAutoBinding(name, value); } } void CustomMaterial::setParamsTexture(SpriteFrame* spriteFrame) { GLProgramState* programState = _getGLProgramState(); if (programState == nullptr || spriteFrame == nullptr) { return; } Texture2D* texture = spriteFrame->getTexture(); Vec2 texRectOffset, texRectSize; // 计算纹理的偏移和大小 Rect rect = spriteFrame->getRectInPixels(); Size atlasSize = spriteFrame->getTexture()->getContentSizeInPixels(); texRectOffset = Vec2(rect.origin.x / atlasSize.width, rect.origin.y / atlasSize.height); texRectSize = Vec2(rect.size.width / atlasSize.width, rect.size.height / atlasSize.height); for (auto ¶m : _paramTexture) { texture->setTexParameters(param.texParams); programState->setUniformTexture(param.name, texture); programState->setUniformVec2("u_texRectOffset", texRectOffset); programState->setUniformVec2("u_texRectSize", texRectSize); } } GLProgramState* CustomMaterial::_getGLProgramState() { if (getTechniques().at(0) && getTechniques().at(0)->getPasses().at(0)) { return getTechniques().at(0)->getPasses().at(0)->getGLProgramState(); } return nullptr; } void CustomMaterial::setRelativePath(std::string relativePath) { auto found = relativePath.find_last_of("/\\"); if (found != std::string::npos) { _relativePath = relativePath.substr(0, found + 1); } else { _relativePath = ""; } } bool CustomMaterial::useBlendFunc() { return _useBlendFunc; } BlendFunc CustomMaterial::getBlendFunc() { return _blendFunc; } std::string CustomMaterial::getRelativePath() { return _relativePath; } void CustomMaterial::setMaterialPath(std::string materialPath) { _materialPath = materialPath; } std::string CustomMaterial::getMaterialPath() { return _materialPath; } static bool isValidUniform(const char* name) { return !(strcmp(name, "defines")==0 || strcmp(name, "vertexShader")==0 || strcmp(name, "fragmentShader")==0); } static const char* getOptionalString(Properties* properties, const char* key, const char* defaultValue) { const char* ret = properties->getString(key); if (!ret) ret = defaultValue; return ret; } NS_CC_END