一、需求
? ? ? ? Cesium支持加載gltf和3dtiles等三維數(shù)據(jù)模型,實現(xiàn)了很好的封裝,往往只需要給一個uri就能加載模型文件,并實現(xiàn)貼圖渲染等。但是好的封裝帶來的問題是如果開發(fā)者想要自定義貼圖,那該怎么辦?不得不從源碼入手。
二、價值
? ? ? ? 這篇文章的價值不僅僅是gltf增加紋理貼圖,因為剛才說到的3dtiles其實也是基于gltf來實現(xiàn)的模型,那么如果想給3dtiles增加自定義貼圖,是否也意味著可以走gltf這條路,并且從gltf這層實現(xiàn)之后,是否意味著對b3dm/i3dm/cmpt等的統(tǒng)一。
三、源碼解讀
1.框架:
? ? ? ?3dtiels中b3dm和i3dm是以gltf為基礎(chǔ)進行加載和渲染的,因此Cesium在封裝的B3dmLoader和I3dmLoader中都有調(diào)用_gltfLoader的地方,其中使用的是:gltfLoader.process
B3dmLoader.prototype.process = function(frameState) {
...
const ready = this._gltfLoader.process(frameState);
...
};
I3dmLoader.prototype.process = function(frameState) {
...
ready = gltfLoader.process(frameState);
...
};
在這個函數(shù)中拋開異常處理邏輯,關(guān)鍵函數(shù)在于:loadResources5(this, frameState):
async function loadResources5(loader, frameState) {
//給出Json指引
const gltf = loader.gltfJson;
//具體的資源加載
const promise = parse(loader, gltf, supportedImageFormats, frameState);
...
//注意這里由于模型資源加載完成后是不需要中間數(shù)據(jù)的,為了減少內(nèi)存的消耗,Cesium這里對Json信息進行了清理
if (defined_default(loader._gltfJsonLoader) && loader._releaseGltfJson) {
ResourceCache_default.unload(loader._gltfJsonLoader);
loader._gltfJsonLoader = void 0;
}
return promise;
}
再來看parse:
function parse(loader, gltf, supportedImageFormats, frameState) {
//...拓展項相關(guān)的數(shù)據(jù)處理
//注意一下結(jié)構(gòu),實際就是按照J(rèn)son的指引,將具體的數(shù)據(jù)請取出,熟悉gltf的Json項自然就明白了各項的含義
const nodes = loadNodes(loader, gltf, supportedImageFormats, frameState);
const skins = loadSkins(loader, gltf, nodes);
const animations = loadAnimations(loader, gltf, nodes);
const articulations = loadArticulations(gltf);
const scene = loadScene(gltf, nodes);
const components = new Components2();
const asset = new Asset2();
const copyright = gltf.asset.copyright;
...
//將取出的數(shù)據(jù)存儲在components中
components.asset = asset;
components.scene = scene;
components.nodes = nodes;
...
loader._components = components;
...
}
至此,數(shù)據(jù)的讀取,處理就完成了,意味著渲染隱含與其中。下面將重點分析。
2.渲染邏輯
A.紋理資源的加載
如果了解gltf的管理方式:
不難看出node是總覽全局的,那么進入 node處理部分:loadNodes::loadNode
function loadNode(loader, gltf, gltfNode, supportedImageFormats, frameState) {
...
//一個node對應(yīng)一個meshId,用于獲取對應(yīng)的mesh
const meshId = gltfNode.mesh;
if (defined_default(meshId)) {
const mesh = gltf.meshes[meshId];
//mesh中又包括多個圖元
const primitives = mesh.primitives;
const primitivesLength = primitives.length;
for (let i = 0; i < primitivesLength; ++i) {
node.primitives.push(
//圖元是最小的渲染可調(diào)度單位
loadPrimitive(
loader,
gltf,
primitives[i],
defined_default(node.instances),
supportedImageFormats,
frameState
)
);
}
...
}
return node;
}
在最小的渲染單位primitive中:
function loadPrimitive(loader, gltf, gltfPrimitive, hasInstances, supportedImageFormats, frameState) {
...
//從圖元取得MaterialId
const materialId = gltfPrimitive.material;
if (defined_default(materialId)) {
//加載材質(zhì)的入口,也意味著材質(zhì)的管理(增刪改)都可以從這里找到
primitive.material = loadMaterial(
loader,
gltf,
gltf.materials[materialId],
supportedImageFormats,
frameState
);
}
...
return primitive;
}
?加載材質(zhì)部分主要包括:初始化一個空材質(zhì)+往材質(zhì)模板中填充數(shù)據(jù)
function loadMaterial(loader, gltf, gltfMaterial, supportedImageFormats, frameState) {
//首先進來的第一件事先創(chuàng)建一個空材質(zhì)用于填充數(shù)據(jù)
const material = new Material3();
...
//直接計算的填充
material.unlit = defined_default(extensions.KHR_materials_unlit);
...
//針對特定類型材質(zhì)特定參數(shù)計算,最后再填充
specularGlossiness.glossinessFactor = pbrSpecularGlossiness.glossinessFactor;
material.pbrSpecularGlossiness = pbrSpecularGlossiness;
...
//重頭戲就是這里的加載紋理
metallicRoughness.baseColorTexture = loadTexture(
loader,
gltf,
pbrMetallicRoughness.baseColorTexture,
supportedImageFormats,
frameState
);
...
return material;
}
?加載紋理的邏輯:
function loadTexture(loader, gltf, textureInfo, supportedImageFormats, frameState, samplerOverride) {
//檢查Image是否可用
//紋理加載器
const textureLoader = ResourceCache_default.getTextureLoader({
gltf,
textureInfo,
gltfResource: loader._gltfResource,
baseResource: loader._baseResource,
supportedImageFormats,
frameState,
asynchronous: loader._asynchronous
});
//紋理解釋器
const textureReader = GltfLoaderUtil_default.createModelTextureReader({
textureInfo
});
//將相關(guān)加載放入總加載器管理
loader._textureLoaders.push(textureLoader);
...
loader._textureState = GltfLoaderState.FAILED;
loader._textureErrors.push(error);
loader._texturesPromises.push(promise);
loader._textureCallbacks[index]...
return textureReader;
}
B.紋理資源應(yīng)用?
當(dāng)紋理加載完成,就要考慮如何消費紋理,即編寫shader和處理:
紋理的使用往往是在FragmentShader中,這塊的編碼在Cesium中為:
var MaterialStageFS_default = "http:// If the style color is white, it implies the feature has not been styled.\nbool isDefaultStyleColor(vec3 color)\n{\n return all(greaterThan(color, vec3(1.0 - czm_epsilon3)));\n}\n\nvec3 blend(vec3 sourceColor, vec3 styleColor, float styleColorBlend)\n{\n vec3 blendColor = mix(sourceColor, styleColor, styleColorBlend);\n vec3 color = isDefaultStyleColor(styleColor.rgb) ? sourceColor : blendColor;\n return color;\n}\n\nvec2 computeTextureTransform(vec2 texCoord, mat3 textureTransform)\n{\n return vec2(textureTransform * vec3(texCoord, 1.0));\n}\n\n#ifdef HAS_NORMALS\nvec3 computeNormal(ProcessedAttributes attributes)\n{\n // Geometry normal. This is already normalized \n vec3 ng = attributes.normalEC;\n\n vec3 normal = ng;\n #if defined(HAS_NORMAL_TEXTURE) && !defined(HAS_WIREFRAME)\n vec2 normalTexCoords = TEXCOORD_NORMAL;\n #ifdef HAS_NORMAL_TEXTURE_TRANSFORM\n normalTexCoords = computeTextureTransform(normalTexCoords, u_normalTextureTransform);\n #endif\n\n // If HAS_BITANGENTS is set, then HAS_TANGENTS is also set\n #ifdef HAS_BITANGENTS\n vec3 t = attributes.tangentEC;\n vec3 b = attributes.bitangentEC;\n mat3 tbn = mat3(t, b, ng);\n vec3 n = texture(u_normalTexture, normalTexCoords).rgb;\n normal = normalize(tbn * (2.0 * n - 1.0));\n #elif (__VERSION__ == 300 || defined(GL_OES_standard_derivatives))\n // If derivatives are available (not IE 10), compute tangents\n vec3 positionEC = attributes.positionEC;\n vec3 pos_dx = dFdx(positionEC);\n vec3 pos_dy = dFdy(positionEC);\n vec3 tex_dx = dFdx(vec3(normalTexCoords,0.0));\n vec3 tex_dy = dFdy(vec3(normalTexCoords,0.0));\n vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t);\n t = normalize(t - ng * dot(ng, t));\n vec3 b = normalize(cross(ng, t));\n mat3 tbn = mat3(t, b, ng);\n vec3 n = texture(u_normalTexture, normalTexCoords).rgb;\n normal = normalize(tbn * (2.0 * n - 1.0));\n #endif\n #endif\n\n #ifdef HAS_DOUBLE_SIDED_MATERIAL\n if (czm_backFacing()) {\n normal = -normal;\n }\n #endif\n\n return normal;\n}\n#endif\n\nvoid materialStage(inout czm_modelMaterial material, ProcessedAttributes attributes, SelectedFeature feature)\n{\n #ifdef HAS_NORMALS\n material.normalEC = computeNormal(attributes);\n #endif\n\n vec4 baseColorWithAlpha = vec4(1.0);\n // Regardless of whether we use PBR, set a base color\n #ifdef HAS_BASE_COLOR_TEXTURE\n vec2 baseColorTexCoords = TEXCOORD_BASE_COLOR;\n\n #ifdef HAS_BASE_COLOR_TEXTURE_TRANSFORM\n baseColorTexCoords = computeTextureTransform(baseColorTexCoords, u_baseColorTextureTransform);\n #endif\n\n baseColorWithAlpha = czm_srgbToLinear(texture(u_baseColorTexture, baseColorTexCoords));\n\n #ifdef HAS_BASE_COLOR_FACTOR\n baseColorWithAlpha *= u_baseColorFactor;\n #endif\n #elif defined(HAS_BASE_COLOR_FACTOR)\n baseColorWithAlpha = u_baseColorFactor;\n #endif\n\n #ifdef HAS_POINT_CLOUD_COLOR_STYLE\n baseColorWithAlpha = v_pointCloudColor;\n #elif defined(HAS_COLOR_0)\n vec4 color = attributes.color_0;\n // .pnts files store colors in the sRGB color space\n #ifdef HAS_SRGB_COLOR\n color = czm_srgbToLinear(color);\n #endif\n baseColorWithAlpha *= color;\n #endif\n\n material.diffuse = baseColorWithAlpha.rgb;\n material.alpha = baseColorWithAlpha.a;\n\n #ifdef USE_CPU_STYLING\n material.diffuse = blend(material.diffuse, feature.color.rgb, model_colorBlend);\n #endif\n\n #ifdef HAS_OCCLUSION_TEXTURE\n vec2 occlusionTexCoords = TEXCOORD_OCCLUSION;\n #ifdef HAS_OCCLUSION_TEXTURE_TRANSFORM\n occlusionTexCoords = computeTextureTransform(occlusionTexCoords, u_occlusionTextureTransform);\n #endif\n material.occlusion = texture(u_occlusionTexture, occlusionTexCoords).r;\n #endif\n\n #ifdef HAS_EMISSIVE_TEXTURE\n vec2 emissiveTexCoords = TEXCOORD_EMISSIVE;\n #ifdef HAS_EMISSIVE_TEXTURE_TRANSFORM\n emissiveTexCoords = computeTextureTransform(emissiveTexCoords, u_emissiveTextureTransform);\n #endif\n\n vec3 emissive = czm_srgbToLinear(texture(u_emissiveTexture, emissiveTexCoords).rgb);\n #ifdef HAS_EMISSIVE_FACTOR\n emissive *= u_emissiveFactor;\n #endif\n material.emissive = emissive;\n #elif defined(HAS_EMISSIVE_FACTOR)\n material.emissive = u_emissiveFactor;\n #endif\n\n #if defined(LIGHTING_PBR) && defined(USE_SPECULAR_GLOSSINESS)\n #ifdef HAS_SPECULAR_GLOSSINESS_TEXTURE\n vec2 specularGlossinessTexCoords = TEXCOORD_SPECULAR_GLOSSINESS;\n #ifdef HAS_SPECULAR_GLOSSINESS_TEXTURE_TRANSFORM\n specularGlossinessTexCoords = computeTextureTransform(specularGlossinessTexCoords, u_specularGlossinessTextureTransform);\n #endif\n\n vec4 specularGlossiness = czm_srgbToLinear(texture(u_specularGlossinessTexture, specularGlossinessTexCoords));\n vec3 specular = specularGlossiness.rgb;\n float glossiness = specularGlossiness.a;\n #ifdef HAS_SPECULAR_FACTOR\n specular *= u_specularFactor;\n #endif\n\n #ifdef HAS_GLOSSINESS_FACTOR\n glossiness *= u_glossinessFactor;\n #endif\n #else\n #ifdef HAS_SPECULAR_FACTOR\n vec3 specular = clamp(u_specularFactor, vec3(0.0), vec3(1.0));\n #else\n vec3 specular = vec3(1.0);\n #endif\n\n #ifdef HAS_GLOSSINESS_FACTOR\n float glossiness = clamp(u_glossinessFactor, 0.0, 1.0);\n #else\n float glossiness = 1.0;\n #endif\n #endif\n\n #ifdef HAS_DIFFUSE_TEXTURE\n vec2 diffuseTexCoords = TEXCOORD_DIFFUSE;\n #ifdef HAS_DIFFUSE_TEXTURE_TRANSFORM\n diffuseTexCoords = computeTextureTransform(diffuseTexCoords, u_diffuseTextureTransform);\n #endif\n\n vec4 diffuse = czm_srgbToLinear(texture(u_diffuseTexture, diffuseTexCoords));\n #ifdef HAS_DIFFUSE_FACTOR\n diffuse *= u_diffuseFactor;\n #endif\n #elif defined(HAS_DIFFUSE_FACTOR)\n vec4 diffuse = clamp(u_diffuseFactor, vec4(0.0), vec4(1.0));\n #else\n vec4 diffuse = vec4(1.0);\n #endif\n czm_pbrParameters parameters = czm_pbrSpecularGlossinessMaterial(\n diffuse.rgb,\n specular,\n glossiness\n );\n material.diffuse = parameters.diffuseColor;\n // the specular glossiness extension's alpha overrides anything set\n // by the base material.\n material.alpha = diffuse.a;\n material.specular = parameters.f0;\n material.roughness = parameters.roughness;\n #elif defined(LIGHTING_PBR)\n #ifdef HAS_METALLIC_ROUGHNESS_TEXTURE\n vec2 metallicRoughnessTexCoords = TEXCOORD_METALLIC_ROUGHNESS;\n #ifdef HAS_METALLIC_ROUGHNESS_TEXTURE_TRANSFORM\n metallicRoughnessTexCoords = computeTextureTransform(metallicRoughnessTexCoords, u_metallicRoughnessTextureTransform);\n #endif\n\n vec3 metallicRoughness = texture(u_metallicRoughnessTexture, metallicRoughnessTexCoords).rgb;\n float metalness = clamp(metallicRoughness.b, 0.0, 1.0);\n float roughness = clamp(metallicRoughness.g, 0.04, 1.0);\n #ifdef HAS_METALLIC_FACTOR\n metalness *= u_metallicFactor;\n #endif\n\n #ifdef HAS_ROUGHNESS_FACTOR\n roughness *= u_roughnessFactor;\n #endif\n #else\n #ifdef HAS_METALLIC_FACTOR\n float metalness = clamp(u_metallicFactor, 0.0, 1.0);\n #else\n float metalness = 1.0;\n #endif\n\n #ifdef HAS_ROUGHNESS_FACTOR\n float roughness = clamp(u_roughnessFactor, 0.04, 1.0);\n #else\n float roughness = 1.0;\n #endif\n #endif\n czm_pbrParameters parameters = czm_pbrMetallicRoughnessMaterial(\n material.diffuse,\n metalness,\n roughness\n );\n material.diffuse = parameters.diffuseColor;\n material.specular = parameters.f0;\n material.roughness = parameters.roughness;\n #endif\n}\n";
?相當(dāng)?shù)拈L,但是這中間有上述分析過程中對材質(zhì)單個參數(shù)(粗糙度,金屬度)和紋理的處理,不妨一讀。那么這段Shader如何使用的呢?
MaterialPipelineStage.process = function(renderResources, primitive, frameState) {
...
processMaterialUniforms(
material,
uniformMap2,
shaderBuilder,
defaultTexture,
defaultNormalTexture,
defaultEmissiveTexture,
disableTextures
);
if (defined_default(material.specularGlossiness)) {
processSpecularGlossinessUniforms(
material,
uniformMap2,
shaderBuilder,
defaultTexture,
disableTextures
);
}
else {
processMetallicRoughnessUniforms(
material,
uniformMap2,
shaderBuilder,
defaultTexture,
disableTextures
);
}
...
shaderBuilder.addFragmentLines(MaterialStageFS_default);
...
};
?是的,直接在最下方添加到ShaderBuilder。
看上去我們這個過程從數(shù)據(jù)獲取到消費似乎是完成了,但是細心的人應(yīng)該發(fā)現(xiàn)了MaterialStageFS_default 還有一些宏或者不同的紋理它的采樣器uv這樣的數(shù)據(jù)其實也是要告知shader的,那么這種處理實際是在processGldLightMapUniforms::processTexture2這個函數(shù)中:
function processTexture2(shaderBuilder, uniformMap2, textureReader, uniformName, defineName, defaultTexture) {
//添加Uniform變量
shaderBuilder.addUniform(
"sampler2D",//類型
uniformName,//變量名
ShaderDestination_default.FRAGMENT//添加到Fragment
);
uniformMap2[uniformName] = function() {
return defaultValue_default(textureReader.texture, defaultTexture);
};
//shaderBuilder.addDefine用于在Shader中定義變量并給初值
//(名稱,默認(rèn)值,添加位置)
const textureDefine = `HAS_${defineName}_TEXTURE`;//宏
shaderBuilder.addDefine(textureDefine, void 0, ShaderDestination_default.FRAGMENT);
const texCoordIndex = textureReader.texCoord;
const texCoordVarying = `v_texCoord_${texCoordIndex}`;
const texCoordDefine = `TEXCOORD_${defineName}`;
//uv
shaderBuilder.addDefine(
texCoordDefine,
texCoordVarying,
ShaderDestination_default.FRAGMENT
);
...
}
至此,整個流程才算完成,理解了以上流程之后,要想加一張紋理那就比較容易了。
3.實操添加一張貼圖
這里給出步驟思路,具體實現(xiàn)自己寫一遍應(yīng)該會好很多:
a.新增一張紋理貼圖,意味著Material要加新成員,對應(yīng)的是loadMaterial中的空材質(zhì)構(gòu)造函數(shù):
const material = new Material3();
b. 對空material填充需要loadTexture,這里要注意紋理解釋器的豐富,封裝在了getAllTextureReaders中;
c.加載好紋理之后就是紋理處理,也就是shader部分,這里一共又可以分為兩步:
? ? ? processTexture2添加uniform數(shù)據(jù)資源,往shader壓入變量及其值;文章來源:http://www.zghlxwxcb.cn/news/detail-460625.html
? ? ? 編寫shader代碼:MaterialStageFS_default。直接在這里改就可以利用上ShaderBuilder的添加一步到位。?文章來源地址http://www.zghlxwxcb.cn/news/detail-460625.html
到了這里,關(guān)于Cesium-源碼修改-gltf增加紋理貼圖改變3dtiles外觀的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!