Cesium原理篇:Material【转】

摘要:
如果我们想要在OpenGL中模拟多种类型的物体,我们必须为每个物体分别定义材质属性。材质的风格有很多种,形状也不尽相同,线面各异,为此,Cesium提供了Material对象,来方便我们设置材质。Fabric我们先来看看Cesium都提供了哪些内建材质类型,以及如何创建对应的Material,我也是参考的Cesium在githubwike上对Fabric的介绍,更详细的内容可以自己去看。在Cesium中,Fabric是描述材质的一种json格式。

https://www.cnblogs.com/fuckgiser/p/6171245.html

Shader

首先,在本文开始前,我们先普及一下材质的概念,这里推荐材质,普及材质的内容都是截取自该网站,我觉得他写的已经够好了。在开始普及概念前,推荐一首我此刻想到的歌《光---陈粒》。

在真实世界里,每个物体会对光产生不同的反应。钢看起来比陶瓷花瓶更闪闪发光,一个木头箱子不会像钢箱子一样对光产生很强的反射。每个物体对镜面高光也有不同的反应。有些物体不会散射(Scatter)很多光却会反射(Reflect)很多光,结果看起来就有一个较小的高光点(Highlight),有些物体散射了很多,它们就会产生一个半径更大的高光。如果我们想要在OpenGL中模拟多种类型的物体,我们必须为每个物体分别定义材质(Material)属性。

我们指定一个物体和一个光的颜色来定义物体的图像输出,并使之结合环境(Ambient)和镜面强度(Specular Intensity)元素。当描述物体的时候,我们可以使用3种光照元素:环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)、镜面光照(Specular Lighting)定义一个材质颜色。通过为每个元素指定一个颜色,我们已经对物体的颜色输出有了精密的控制。现在把一个镜面高光元素添加到这三个颜色里,这是我们需要的所有材质属性:

复制代码
struct Material
{
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
};
复制代码

以上是对材质的一个最简单概括,我们下面进入Cesium的环节。先来看看Cesium在Shader中对Material的定义:

复制代码
struct czm_material
{
    vec3 diffuse;
    float specular;
    float shininess;
    vec3 normal;
    vec3 emission;
    float alpha;
};
复制代码

和上面给出的结构体大致相同,区别是少了环境光ambient,但多了法向量normal,自发光emission和alpha,我们带着这个疑问看一下Cesium处理材质的片段着色器:

复制代码
varying vec3 v_positionEC;
varying vec3 v_normalEC;
void main()
{
    vec3 positionToEyeEC = -v_positionEC;
    vec3 normalEC = normalize(v_normalEC);
#ifdef FACE_FORWARD
    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);
#endif
    czm_materialInput materialInput;
    materialInput.normalEC = normalEC;
    materialInput.positionToEyeEC = positionToEyeEC;
    czm_material material = czm_getDefaultMaterial(materialInput);
    gl_FragColor = czm_phong(normalize(positionToEyeEC), material);
}
复制代码

此时的坐标系是以相机为中心点,首先获取当前点的位置和法向量,通过czm_getMaterial获取默认的一个材质对象,gl_FragColor通过czm_phong方法得到对应的颜色。对于phong,在OpenGL SuperBible里面有详细的说明,大概就是通过material的属性,根据光的位置和光的颜色,最终计算出在该点当前环境和自身材质的影响下对应的颜色。我们来看看czm_phong的实现:

复制代码
vec4 czm_phong(vec3 toEye, czm_material material)
{
    float diffuse = czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 0.0, 1.0), material);
    if (czm_sceneMode == czm_sceneMode3D) {
        diffuse += czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 1.0, 0.0), material);
    }
    float specular = czm_private_getSpecularOfMaterial(czm_sunDirectionEC, toEye, material) + czm_private_getSpecularOfMaterial(czm_moonDirectionEC, toEye, material);
    vec3 materialDiffuse = material.diffuse * 0.5;
    vec3 ambient = materialDiffuse;
    vec3 color = ambient + material.emission;
    color += materialDiffuse * diffuse;
    color += material.specular * specular;
    return vec4(color, material.alpha);
}
复制代码

如上是phong颜色计算的算法,我并没有给出getLambertDiffuse和getSpecular的具体代码,都是光的基本物理规律。这里要说的是getLambertDiffuse的参数,如果是球面物体时,会调用czm_private_phong,此时参数为czm_sunDirectionEC,也就是太阳的位置,而这里认为光源的位置是靠近相机的某一个点,另外,环境光ambient默认是反射光的一半,这个也说的过去,最后我们看到最终颜色的alpha位是material.alpha。

上面是Shader中涉及到材质的一个最简过程:材质最终影响的是片段着色器中的颜色gl_FragColor,而所有czm_开头的都是Cesium内建的方法和对象,Cesium已经帮我们提供好了光学模型和计算方法,并不需要我们操心,而我们要做的,就是指定对应物体的材质属性,通过修改material中的属性值,来影响最终的效果。所以,接下来的问题就是如何指定物体的材质属性。

材质的风格有很多种,形状也不尽相同,线面各异,为此,Cesium提供了Material对象,来方便我们设置材质。

Fabric

我们先来看看Cesium都提供了哪些内建材质类型,以及如何创建对应的Material,我也是参考的Cesium在github wike上对Fabric的介绍,更详细的内容可以自己去看。在Cesium中,Fabric是描述材质的一种json格式。材质可以很简单,就是对象表面的一个贴图,也可以是一个图案,比如条形或棋盘形。

1

比如ImageType类型,Cesium提供了如下两种方式来设置:

复制代码
// 方法一
primitive.appearance.material = new Cesium.Material({
    fabric : {
        type : 'Image',
        uniforms : {
            image : '../images/Cesium_Logo_Color.jpg'
        }
    }
});
// 方法二
primitive.appearance..material = Material.fromType('Image');
primitive.appearance..uniforms.image = 'image.png';
复制代码

Cesium默认提供了十八个类型:

  • ColorType
  • ImageType
  • DiffuseMapType
  • AlphaMapType
  • SpecularMapType
  • EmissionMapType
  • BumpMapType
  • NormalMapType
  • GridType
  • StripeType
  • CheckerboardType
  • DotType
  • WaterType
  • RimLightingType
  • FadeType
  • PolylineArrowType
  • PolylineGlowType
  • PolylineOutlineType

当然,Cesium支持多个Type的叠加效果,如下是DiffuseMap和NormalMap的一个叠加,components中指定material中diffuse、specular、normal的映射关系和值:

复制代码
primitive.appearance.material = new Cesium.Material({
    fabric : {
        materials : {
            applyDiffuseMaterial : {
                type : 'DiffuseMap',
                uniforms : {
                    image : '../images/bumpmap.png'
                }
            },
            normalMap : {
                type : 'NormalMap',
                uniforms : {
                    image : '../images/normalmap.png',
                    strength : 0.6
                }
            }
        },
        components : {
            diffuse : 'diffuseMaterial.diffuse',
            specular : 0.01,
            normal : 'normalMap.normal'
        }
    }
});
复制代码

当然,这些都满足不了你的欲望?你也可以自定义一个自己的MaterialType,我们先了解Cesium.Material的内部实现后,再来看看自定义Material。

Material

用户通常只需要指定type,uniforms,components三个属性,构建一个Fabric的JSON。这是因为Material在初始化时,会加载上述默认的十八个类型,比如对应的ColorType代码:

复制代码
Material.ColorType = 'Color';
Material._materialCache.addMaterial(Material.ColorType, {
    fabric : {
        type : Material.ColorType,
        uniforms : {
            color : new Color(1.0, 0.0, 0.0, 0.5)
        },
        components : {
            diffuse : 'color.rgb',
            alpha : 'color.a'
        }
    },
    translucent : function(material) {
        return material.uniforms.color.alpha < 1.0;
    }
});
// 创建material
polygon.material = Cesium.Material.fromType('Color');
polygon.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
复制代码

其他的类型也大概相同,在初始化的时候已经全部构建。因此,用户在执行创建时,已经有了一个ColorMaterial,只是对里面的一些属性修改为自己的期望值的过程。我们具体Material.fromType的具体内容:

复制代码
Material.fromType = function(type, uniforms) {
    var material = new Material({
        fabric : {
            type : type
        }
    });
    return material;
};
function Material(options) {
    initializeMaterial(options, this);
    if (!defined(Material._uniformList[this.type])) {
        Material._uniformList[this.type] = Object.keys(this._uniforms);
    }
}
function initializeMaterial(options, result) {
    var cachedMaterial = Material._materialCache.getMaterial(result.type);
    createMethodDefinition(result);
    createUniforms(result);
    // translucent
}
复制代码

initializeMaterial则是其中的重点,里面有三个关键点:1createMethodDefinition,2createUniforms,3translucent,我们来看看都做了什么

复制代码
function createMethodDefinition(material) {
    // 获取components属性
    // ColorType:{ diffuse : 'color.rgb', alpha : 'color.a'}
    var components = material._template.components;
    var source = material._template.source;
    if (defined(source)) {
        material.shaderSource += source + '
';
    } else {
        material.shaderSource += 'czm_material czm_getMaterial(czm_materialInput materialInput)
{
';
        material.shaderSource += 'czm_material material = czm_getDefaultMaterial(materialInput);
';
        if (defined(components)) {
            for ( var component in components) {
                if (components.hasOwnProperty(component)) {
                    // 根据components中的属性,修改Material中对应属性的获取方式
                    material.shaderSource += 'material.' + component + ' = ' + components[component] + ';
';
                }
            }
        }
        // 封装得到片段着色器中获取material的函数
        material.shaderSource += 'return material;
}
';
    }
}
复制代码

如上是Key1的作用,拼装出片段着色器中获取material的函数,如果Type是Color下,获取的函数代码如下:

复制代码
czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    material.diffuse = color.rgb;
    material.alpha = color.a;
    return material;
}
复制代码

可以对照ColorType的FabricComponents属性,对号入座。下面就是对Fabric的uniforms属性的解析过程了:createUniforms。这里主要有两个作用,第一,根据uniforms,在片源着色器中声明对应的uniform变量,比如ColorType中uniform对应的color变量,则需要声明该变量,当然cesium做了一个特殊的处理,给他们一个标号,保证唯一:更新后的代码如下:

复制代码
uniform vec4 color_0;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    material.diffuse = color_0.rgb;
    material.alpha = color_0.a;
    return material;
}
复制代码

第二个作用是为后面的uniformMap做准备,声明了变量了,当然需要准备好该变量的赋值,建立好这个key-value的过程,保存到material._uniforms数组中:

复制代码
function createUniform(material, uniformId) {
    // 根据变量的类型,建立对应的return value方法
    if (uniformType === 'sampler2D') {
        material._uniforms[newUniformId] = function() {
            return material._textures[uniformId];
        };
        material._updateFunctions.push(createTexture2DUpdateFunction(uniformId));
    } else if (uniformType === 'samplerCube') {
        material._uniforms[newUniformId] = function() {
            return material._textures[uniformId];
        };
        material._updateFunctions.push(createCubeMapUpdateFunction(uniformId));
    } else if (uniformType.indexOf('mat') !== -1) {
        var scratchMatrix = new matrixMap[uniformType]();
        material._uniforms[newUniformId] = function() {
            return matrixMap[uniformType].fromColumnMajorArray(material.uniforms[uniformId], scratchMatrix);
        };
    } else {
        material._uniforms[newUniformId] = function() {
            return material.uniforms[uniformId];
        };
    }
}
复制代码

createUniforms方法后则是对translucent的处理,这个会影响到Pimitive创建RenderState,以及渲染队列的设置。将Fabric中的translucent方法保存在material._translucentFunctions中。

Primitive

此时,我们已经创建好一个color类型的Material,将其赋给对应的Primitive,代码如下:

primitive.appearance.material = Cesium.Material.fromType('Color');

这里出现了一个新的的对象:Appearance。这里,Material只是负责片段着色器中,材质部分的代码,而Appearance则负责该Primitvie整个Shader的代码,包括顶点着色器和片段着色器两个部分,同时,需要根据Appearance的状态来设置对应的RenderState,可以说Appearance是在Material之上的又一层封装。一共有MaterialAppearance、EllipsoidSurfaceAppearance等六类,大同小异,每个对象的属性值不同,但逻辑上统一有Appearance来负责。我们看如下一个Primitive的创建:

复制代码
var rectangle = scene.primitives.add(new Cesium.Primitive({
    geometryInstances : new Cesium.GeometryInstance({
        geometry : new Cesium.RectangleGeometry({
            rectangle : Cesium.Rectangle.fromDegrees(-120.0, 20.0, -60.0, 40.0),
            vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
        })
    }),
    appearance : new Cesium.EllipsoidSurfaceAppearance({
        aboveGround : false
    })
}));
复制代码

如上创建的是一个EllipsoidSurfaceAppearance,创建时如果没有指定Material,则内部默认采用ColorTyoe的材质。当执行Primitive.update时,Appearance的就发挥了自己的价值:

Primitive.prototype.update = function(frameState) {
    createRenderStates(this, context, appearance, twoPasses);
    createShaderProgram(this, frameState, appearance);
    createCommands(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState);
}

首先Appearance基类提供了默认的defaultRenderState,也提供了getRenderState的方法,如下:

复制代码
Appearance.getDefaultRenderState = function(translucent, closed, existing) {
    var rs = {
        depthTest : {
            enabled : true
        }
    };
    if (translucent) {
        rs.depthMask = false;
        rs.blending = BlendingState.ALPHA_BLEND;
    }
    if (closed) {
        rs.cull = {
            enabled : true,
            face : CullFace.BACK
        };
    }
    if (defined(existing)) {
        rs = combine(existing, rs, true);
    }
    return rs;
};
Appearance.prototype.getRenderState = function() {
    var translucent = this.isTranslucent();
    var rs = clone(this.renderState, false);
    if (translucent) {
        rs.depthMask = false;
        rs.blending = BlendingState.ALPHA_BLEND;
    } else {
        rs.depthMask = true;
    }
    return rs;
};
复制代码

然后,各个子类按照自己的需要,看是否使用基类的方法,还是自己有特殊用处,比如EllipsoidSurfaceAppearance类:

复制代码
function EllipsoidSurfaceAppearance(options) {
    this._vertexShaderSource = defaultValue(options.vertexShaderSource, EllipsoidSurfaceAppearanceVS);
    this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, EllipsoidSurfaceAppearanceFS);
    this._renderState = Appearance.getDefaultRenderState(translucent, !aboveGround, options.renderState);
}
EllipsoidSurfaceAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;
function createRenderStates(primitive, context, appearance, twoPasses) {
    var renderState = appearance.getRenderState();
}
复制代码

这样,EllipsoidSurfaceAppearance采用自己的顶点着色器和片段着色器的代码,但RenderState和getRenderState方法都直接用的基类的,因此,当primitive调用createRenderStates方法时,尽管当前的appearance可能类型不一,但确保都有统一一套调用接口,最终创建满足当前需要的RS,当然,这里主要是translucent的区别。

接着,就是创建ShaderProgram:

复制代码
function createShaderProgram(primitive, frameState, appearance) {
    var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource);
    var fs = appearance.getFragmentShaderSource();
}
Appearance.prototype.getFragmentShaderSource = function() {
    var parts = [];
    if (this.flat) {
        parts.push('#define FLAT');
    }
    if (this.faceForward) {
        parts.push('#define FACE_FORWARD');
    }
    if (defined(this.material)) {
        parts.push(this.material.shaderSource);
    }
    parts.push(this.fragmentShaderSource);
    return parts.join('
');
};
复制代码

这里代码比较清楚,就是通过Appearance获取vs和fs,这里多了一个batchTable,这是因为该Primitive可能是批次的封装,因此需要把batch部分的vs和appearance的vs合并,batchTable后面有时间的话,在单独介绍。这里可以看到getFragmentShaderSource,增加了一下宏,同时,在Appearance中,不仅有自己的fragmentShaderSource,同时也把我们之前在Material中封装的material.shaderSource也追加进去。真的是海纳百川的历程。

这样,就来到最后一步,构建Command:

复制代码
function createCommands(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands, frameState) {
    var uniforms = combine(appearanceUniformMap, materialUniformMap);
    uniforms = primitive._batchTable.getUniformMapCallback()(uniforms);
    var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
    // ……
    colorCommand.uniformMap = uniforms;
    colorCommand.pass = pass;
    //……    
}
复制代码

可见,Material的uniforms合并后绑定到了command的uniformMap中,另外translucent也用来判断渲染队列。至此,Material->Appearance->Renderer的整个过程就结束了。可见,Material主要涉及到初始化和Primitive.update部分。

当然,之前我们介绍过,通过创建Entity的方式,也可以通过DataSourceDisplay这个过程最终创建Primitive并添加到PrimitiveCollection这种方式。这和直接构建Primitive基本相似,只是多绕了一圈。当然,这一圈也不是白绕的,因为会做批次的处理,合并多个风格相似的Geometry。当然,这就牵扯到Batch,Appearance以及MaterialProperty之间的关系我们后续再介绍这种创建方式下的不同之处。

免责声明:文章转载自《Cesium原理篇:Material【转】》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇PowerDesigner中如何导入表结构在SAP中如何实现现金流量表? FI Monthly Closed下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

BROCADE交换机配置

BROCADE交换机配置一 与交换机交互,可以使用三种方式: 串口 以太网口 光纤口 缺省的串口参数是:9600,N,8,1 缺省IP访问方式是: IP地址: 10.77.77.77 用户名: admin 密码:password 时区: Pacific time 交换机名称:switch 可使用ipAddrSet命令来重新设置交换机的IP地址。 在200...

unity中动态处理模型透明或材质的方法

1.改shader using System.Collections.Generic; using UnityEngine; /// <summary> /// 枚举Shader4种状态类型 /// </summary> public enum RenderingMode { Opaque, Cutout,...

Unity MeshRender更换材质球方法

https://blog.csdn.net/ystistheking/article/details/70207792 转载自CSDN布莱克汉; 干活的时候遇到了这样一个问题,当要用代码给这个模型换材质球的时候,单独获取renderer组件里的materials数组里的materials[1]或者materials[2]是无法改变材质球的,此时采用以下...

Unity CombineChildren和MeshCombineUtility

原理 Unity3D如何通过CombineChildren和MeshCombineUtility优化场景? 首先解释下联结的原理和意思:文档里说,显卡对于一个含100个面片的物体的和含1500个面片的物体的渲染消耗几乎是等价的。所以如果你有N个同一材质的东西,那么把他们联成同一个物体再统一用一个material那么对于显卡的渲染消耗就要降低N倍。 方法 1...

Hyperledger Fabric 安装

  前一篇已经准备好了运行环境,本篇将介绍Fabric的安装以及测试网络环境   Fabric的安装   HyperFabric安装大致可以分成两种,     第一:使用自带在脚本安装,可以下载fabric-sample和二进制文件到操作系统,简化安装过程。     第二:以源码在方式进行本地编译安装,此方式相对第一种比较复杂,需手动编译生存相应工具。  ...

IDEA更改主题插件——Material Theme UI详解

一名小码农 今天介绍IDEA中一款强大的颜值插件:Material Theme UI 官方文档:https://www.material-theme.com/docs/introduction/ 话不多说,先上图:(颜色不喜欢可以更改) 一、安装 1:在File>Settings>Plugins中直接搜索插件,找到之后下载即可。...