Skip to content

RoyMaterialTemplate与RoyMaterialInstance

在RoyEngine中,材质由两部分组成:材质模板和材质实例。

  • 材质模板约定了材质的基础,包括:

    • Shader,即GPU执行指令
    • Uniform,即材质参数的形式(名称+类型)。
    • 渲染参数:是否透明、混合模式、是否双面等
  • 材质实例从材质模板中创建出来,其要做的操作就是把材质参数的数值与材质模板结合到一起,形成完整的可用于RoyObject3D渲染的数据结构体。

  • 举个例子,材质模板中定义了color这样一个材质参数,创建三个材质实例并分别赋给三个球,这三个实例对应color的值是红色、黄色、蓝色,那么这三个球就是红球、黄球、蓝球。

材质模板的生成:

  • 材质模板的源文件是一个json形式的问题,目前所有项目中使用到的材质文件位于目录libs/royengine/assets/materials下,使用matc(位于libs/royengine/assets/matc)进行编译,编译的产物放入libs/royengine/libs/materials下面。
  • 我们摘一个典型的roymat文件如下并做简要分析。
material {
    name : base_${SHADINGMODEL}_${BLENDING},
    requires : [ uv0, uv1 ],
    normalOnly : true,
    
    shadingModel : ${SHADINGMODEL},
    blending : ${BLENDING},
    doubleSided : ${DOUBLESIDED},
    transparency : ${TRANSPARENCY},
    flipUV : false,
    draftSupport : true,
    specularAmbientOcclusion : simple,
    specularAntiAliasing : true,
    clearCoatIorChange : false,
    reflections : screenspace,

    parameters : [
        { type : float4[4], name : clippingPlanes },
        { type : float4, name : clippingInfo },

        { type : float3, name : specularFactor },
        { type : float, name : glossinessFactor },

        { type : int, name : meshID },

        // Base Color
        { type : int, name : baseColorMapIndex },
        { type : float4, name : baseColorFactor },
        { type : sampler2d, name : baseColorMap },
        { type : mat3, name : baseColorUvMatrix, precision: high },

        // Metallic-Roughness Map
        { type : int, name : metallicRoughnessMapIndex },
        { type : float, name : metallicFactor },
        { type : float, name : roughnessFactor },
        { type : sampler2d, name : metallicRoughnessMap },

        // Emissive Map
        { type : int, name : emissiveMapIndex },
        { type : float3, name : emissiveFactor },
        { type : float, name : emissiveStrength },
        { type : sampler2d, name : emissiveMap },

        // Reflectance
        { type : float, name : reflectance }

        ${CUSTOM_PARAMS}
    ],
}

vertex {

    #include "../common/getMeshID_vs.glsl"

    void materialVertex(inout MaterialVertexInputs material) {
        ${CUSTOM_VERTEX}

        vertex_meshID.w = getMeshID() * 0.001;
    }
}

fragment {
    
    #include "../common/materialClipping_fs.glsl"
    
    void material(inout MaterialInputs material) {
        highp float2 uvs[2];
        uvs[0] = getUV01();
        uvs[1] = getUV1();

        prepareMaterial(material);

        viewClipping();
        materialClipping();

        material.kApplyAO = 1.0;
        material.baseColor = materialParams.baseColorFactor;

        if (materialParams.baseColorMapIndex > -1) {
            highp float2 uv = uvs[materialParams.baseColorMapIndex];
            uv = (vec3(uv, 1.0) * materialParams.baseColorUvMatrix).xy;
            vec4 texColor = texture(materialParams_baseColorMap, uv);
            texColor.rgb = rgb_from_srgb(texColor.rgb);
            material.baseColor *= texColor;
        }

        #if defined(BLEND_MODE_TRANSPARENT)
            material.baseColor.rgb *= material.baseColor.a;
        #endif
        
        // material.baseColor *= getColor();

        #if !defined(SHADING_MODEL_UNLIT)

            #if defined(SHADING_MODEL_LIT)
                material.roughness = materialParams.roughnessFactor;
                material.metallic = materialParams.metallicFactor;
            #endif

            material.emissive = vec4(materialParams.emissiveStrength *
                    materialParams.emissiveFactor.rgb, 0.0);

            #if defined(SHADING_MODEL_SPECULAR_GLOSSINESS)
                material.glossiness = materialParams.glossinessFactor;
                material.specularColor = materialParams.specularFactor;
            #else
                material.reflectance = materialParams.reflectance;
            #endif

            if (materialParams.metallicRoughnessMapIndex > -1) {
                highp float2 uv = uvs[materialParams.metallicRoughnessMapIndex];

                #if defined(SHADING_MODEL_SPECULAR_GLOSSINESS)
                    vec4 sg = texture(materialParams_metallicRoughnessMap, uv);
                    material.specularColor *= rgb_from_srgb(sg.rgb);
                    material.glossiness *= sg.a;
                #else
                    vec4 mr = texture(materialParams_metallicRoughnessMap, uv);
                    material.roughness *= mr.g;
                    material.metallic *= mr.b;
                #endif
            }

            if (materialParams.emissiveMapIndex > -1) {
                highp float2 uv = uvs[materialParams.emissiveMapIndex];
                material.emissive.rgb *= rgb_from_srgb(texture(materialParams_emissiveMap, uv).rgb);
            }
        #endif

        ${CUSTOM_FRAGMENT}

        material.meshId = floor(vertex_meshID.w * 1000.0 + 0.5);
    }
}

vertexWGSL {

    #include "../common/getMeshID_vs.wgsl"

    fn materialVertex(material:ptr<function, MaterialVertexInputs>) {
        ${CUSTOM_VERTEX}
        vertex_meshID.w = getMeshID() * 0.001;
    }
}

fragmentWGSL {

    #include "../common/materialClipping_fs.wgsl"

    fn material(material:ptr<function, MaterialInputs>) {
        var uvs: array<float2, 2>;
        uvs[0] = getUV01();
        uvs[1] = getUV1();

        prepareMaterial(*material);

        viewClipping();
        materialClipping();

        (*material).kApplyAO = 1.0;
        (*material).baseColor = materialParams.baseColorFactor;

        if (materialParams.baseColorMapIndex > -1) {
            var uv: float2 = uvs[materialParams.baseColorMapIndex];
            uv = (vec3f(uv, 1.0) * materialParams.baseColorUvMatrix).xy;
            var texColor: vec4f = textureSample(materialParams_baseColorMap, materialParams_baseColorMapSampler, uv);
			let temp: vec3f = rgb_from_srgb(texColor.rgb);
            texColor.r = temp.r;
			texColor.g = temp.g;
			texColor.b = temp.b;
            (*material).baseColor *= texColor;
        }

        if (BLEND_MODE_TRANSPARENT) {
		    let temp: vec3f = (*material).baseColor.rgb * (*material).baseColor.a;
		    (*material).baseColor.r = temp.r;
			(*material).baseColor.g = temp.g;
			(*material).baseColor.b = temp.b;
		}

       // (*material).baseColor *= getColor();

        if (!SHADING_MODEL_UNLIT) {
		    if (SHADING_MODEL_LIT) {
			    (*material).roughness = materialParams.roughnessFactor;
                (*material).metallic = materialParams.metallicFactor;
			}
			
            (*material).emissive = vec4f(materialParams.emissiveStrength * materialParams.emissiveFactor.rgb, 0.0);
					
			if (SHADING_MODEL_SPECULAR_GLOSSINESS) { 
			    (*material).glossiness = materialParams.glossinessFactor;
                (*material).specularColor = materialParams.specularFactor;
			} else {
			    (*material).reflectance = materialParams.reflectance;
			}

            if (materialParams.metallicRoughnessMapIndex > -1) {
                var uv: float2 = uvs[materialParams.metallicRoughnessMapIndex];

                if (SHADING_MODEL_SPECULAR_GLOSSINESS) {
				    let sg: vec4f = textureSample(materialParams_metallicRoughnessMap, materialParams_metallicRoughnessMapSampler, uv);
                    (*material).specularColor *= rgb_from_srgb(sg.rgb);
                    (*material).glossiness *= sg.a;
				} else {
				    let mr: vec4f = textureSample(materialParams_metallicRoughnessMap, materialParams_metallicRoughnessMapSampler, uv);
                    (*material).roughness *= mr.g;
                    (*material).metallic *= mr.b;
				}
            }

            if (materialParams.emissiveMapIndex > -1) {
                var uv: float2 = uvs[materialParams.emissiveMapIndex];
				let temp: vec3f = (*material).emissive.rgb * rgb_from_srgb(textureSample(materialParams_emissiveMap, materialParams_emissiveMapSampler, uv).rgb);
                (*material).emissive.r = temp.r;
				(*material).emissive.g = temp.g;
				(*material).emissive.b = temp.b;
            }
		}
 
        ${CUSTOM_FRAGMENT}

        (*material).meshId = floor(vertex_meshID.w * 1000.0 + 0.5);
    }
}

其中:

  • material项时材质的基本信息
  • vertex项是顶点着色器中的用户自定义函数。
  • fragment项是像素着色器中的用户自定义函数。

一些简要说明:

  • 材质的编译过程是字符串的拼合过程,根据材质模板定义的参数,选择和生成正确的字符串,并配合材质文件中定义的自定义函数,共同生成最终的运行时文件。
  • 最终的运行时文件包含由材质参数变体形成的多种shader文件的组合,在实际的RoyObject3D去做绘制时,引擎会选择正确的shader来上传到GPU去执行,这也解释了为什么诸如场景参数或者相机参数变化时,引擎能够立刻渲染,因为在这之前已经生成了所有情况的的shader文件。

在使用上:

  • RoyMaterialTemplate需要在onOnit方法中注册给RoyMaterialTemplateMgr,这样就可以在各个插件或组件中,通过名称来获取到材质模板。
  • 拿到RoyMaterialTemplate后就可以创建RoyMaterialInstance,主要的方法:
    • setUniform: 设置材质参数
    • setTexture:设置纹理,纹理只是材质参数的另外一种表达形式而已
    • setSampler:设置纹理采样器,决定纹理以何种方式采样(点采样、双线性采样、三线性采样)
  • 将RoyMaterialInstance赋值给组件,如RoyMeshComponent,组件会将材质实例本身交给其衍生的RoyObject3D上。
  • 具体的使用参考可以参考libs\royframe\royframebase\src\royresource\matgltf\Mat*.ts

对于RoyMaterial的属性,其在RoyProtocal中的结构体对应,摘要如下:

typescript
/**
 * Interface for uniform data in a material instance
 */
interface RoyMatUniformData {
    /** Name of the uniform */
    name: string;
    /** Data of the uniform */
    type: string;
    /** Data of the uniform */
    dataFloatArray?: number[];
    /** Data of the uniform */
    dataFloat?: number;
    /** Data of the uniform */
    dataInt?: number;
    /** Data of the uniform */
    dataBool?: boolean;
    /** Data of the uniform */
    dataStr?: string;
}
/**
 * Interface for texture data in a material instance
 */
interface RoyMatTextureData {
    /** Name of the texture */
    name: string;
    /** Data of the texture */
    data: string;
    /** Sampler information for the texture */
    sampler?: RoySamplerInfoData;
}
/**
 * Interface for sampler information.
 */
interface RoySamplerInfoData {
    /** The minification filter. */
    minFilter?: string;
    /** The magnification filter. */
    magFilter?: string;
    /** The wrap mode for the S coordinate. */
    wrapModeS?: string;
    /** The wrap mode for the T coordinate. */
    wrapModeT?: string;
}
/**
 * Interface for material data
 */
interface RoyMaterialData extends RoyTransDataInfo {
    /** Name of the material template */
    templateName: string;
    /** Blend mode */
    blendMode?: string;
    /** Shading mode */
    shadingMode?: string;
    /** Culling face mode */
    cullFaceMode?: string;
    /** Depth write enabled flag */
    depthWriteEnabled?: boolean;
    /** Depth test enabled flag */
    depthTestEnabled?: boolean;
    /** Double sided flag */
    doubleSided?: boolean;
    /** Color write flag */
    colorWrite?: boolean;
    /** Polygon offset */
    polygonOffset?: number[];
    /** Instance enabled flag */
    instanceEnabled?: boolean;
    /** Light enabled flag */
    lightEnabled?: boolean;
    /** List of uniform data */
    uniformList?: RoyMatUniformData[];
    /** List of texture data */
    textureList?: RoyMatTextureData[];
    /** Name of material*/
    name: string;
}