為實(shí)現(xiàn)三維模型的更炫、更酷、更美觀,Cesium在1.46的版本中新增了場(chǎng)景的后期處理(Post Processing)功能,包括模型描邊、黑白圖、明亮度調(diào)整、夜市效果、環(huán)境光遮蔽,也包括雷達(dá)掃描、原型擴(kuò)散等一些特效。今天我們來(lái)學(xué)習(xí)一下場(chǎng)景后期處理的基礎(chǔ)知識(shí)和實(shí)現(xiàn)流程。
場(chǎng)景后期處理流程
場(chǎng)景的后期處理這個(gè)詞比較陌生,但說(shuō)起照片的PS大家都很熟悉,這兩個(gè)過(guò)程非常類似。日常生活中我們拍攝完照片之后,發(fā)現(xiàn)太亮或太暗,又或者是皮膚不夠白、臉上痘痘明顯,我們可以調(diào)整亮度、修復(fù)一下嫩白的臉蛋,經(jīng)過(guò)幾波操作之后,得到了一張我們非常滿意的照片。

我們可以把照片的修復(fù)過(guò)程簡(jiǎn)單理解成場(chǎng)景的后期處理過(guò)程,修圖的過(guò)程就比喻成對(duì)三維場(chǎng)景中初始渲染的效果進(jìn)行再處理,比如添加物體描邊、明暗度調(diào)整、夜市效果等,最終把綜合之后的效果在場(chǎng)景中渲染出來(lái)。Cesium中的場(chǎng)景后期處理的大概流程如下圖所示:

下面結(jié)合Cesium本身的PostProcess類,詳細(xì)的說(shuō)明一下處理流程:
第一步:通過(guò)PostProcessStageLibrary創(chuàng)建一個(gè)或者多個(gè)后處理效果對(duì)象,得到多個(gè)PostProcessStage或PostProcessStageComposite;
第二步:將他們加入到PostProcessStageCollection對(duì)象中,并設(shè)置PostProcessStage或PostProcessStageComposite一些參數(shù),如uniforms;
第三步:PostProcessStageCollection對(duì)象就會(huì)按照加入的順序進(jìn)行屏幕后期處理,在所有的效果都處理完畢后,最后繪制到屏幕上。
當(dāng)然也可以省略第一步,直接利用PostProcessStageCollection實(shí)例化對(duì)象中已有的處理效果去實(shí)現(xiàn),如ambientOcclusion、bloom、fxaa。
場(chǎng)景后期處理相關(guān)類
上述提到了PostProcess類,基本上涉及到4個(gè)類文件,具體每個(gè)類的作用又是什么呢?我們來(lái)說(shuō)明一下。
(1)PostProcessStage
對(duì)應(yīng)于某個(gè)具體的后期處理效果,它的輸入為場(chǎng)景渲染圖或者上一個(gè)后期處理的結(jié)果圖,輸出結(jié)果是一張?zhí)幚砗蟮膱D片。
// Simple stage to change the colorvar
fs =
'uniform sampler2D colorTexture;\n' +
'varying vec2 v_textureCoordinates;\n' +
'uniform float scale;\n' +
'uniform vec3 offset;\n' +
'void main() {\n' +
' vec4 color = texture2D(colorTexture, v_textureCoordinates);\n' +
' gl_FragColor = vec4(color.rgb * scale + offset, 1.0);\n' +
'}\n';
scene.postProcessStages.add(new Cesium.PostProcessStage({
fragmentShader : fs,
uniforms : {
scale : 1.1,
offset : function() {
return new Cesium.Cartesian3(0.1, 0.2, 0.3);
}
}}));
fragmentShader:片源著色器代碼字符串,它是GLSL代碼語(yǔ)言,需要成對(duì)配置頂點(diǎn)著色器和片元著色器。
uniforms:片源著色器代碼字符串中需要在前端傳入的變量。
(2)PostProcessStageComposite
一個(gè)集合對(duì)象,按順序存儲(chǔ)了不同的場(chǎng)景處理對(duì)象,存儲(chǔ)類型為PostProcessStage或者PostProcessStageComposite的元素,并存儲(chǔ)在stages屬性中。
// Example 1: separable blur filter
// The input to blurXDirection is the texture rendered to by the scene or the output of the previous stage.
// The input to blurYDirection is the texture rendered to by blurXDirection.
scene.postProcessStages.add(new Cesium.PostProcessStageComposite({
stages : [blurXDirection, blurYDirection]}));
(3)PostProcessStageLibrary
負(fù)責(zé)創(chuàng)建具體的后期處理效果,提供了一些創(chuàng)建常用場(chǎng)景特效的方法,包括createBlackAndWhiteStage-黑色和白色漸變渲染、createBlurStage-高斯模、createBrightnessStage-紋理飽和、createDepthOfFieldStage-景深效果等,創(chuàng)建返回的結(jié)果是PostProcessStageComposite或者PostProcessStage類型。相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,直接調(diào)用即可。
var stages = viewer.scene.postProcessStages;
var silhouette = stages.add(
Cesium.PostProcessStageLibrary.createSilhouetteStage()
);
(4)PostProcessStageCollection
是一個(gè)集合類型的類,負(fù)責(zé)管理和維護(hù)放到集合中的PostProcessStage或PostProcessStageComposite類型對(duì)象,實(shí)例化對(duì)象可通過(guò)viewer.scene.postProcessStages直接獲取,提供了一些常用的方法,如add、contains、destroy、remove等。
但需要注意的是,該集合中也設(shè)定了三個(gè)ambientOcclusion、bloom、fxaa效果,如果此類中的環(huán)境光遮擋-ambientOcclusion或發(fā)光效果-bloom被啟用,它們將在所有其他階段之前執(zhí)行,優(yōu)先級(jí)最高;如果近似抗鋸齒-fxaa被啟用,它將在所有其他階段之后執(zhí)行,優(yōu)先級(jí)最低。
場(chǎng)景后期處理效果
Cesium為我們提供了一些默認(rèn)的示例效果,但基本上可分為如下三類:
(1)利用PostProcessStageCollection集合類提供的三個(gè)效果
包括ambientOcclusion環(huán)境光遮擋、bloom發(fā)光效果、fxaa近似抗鋸齒,我們挑選前兩個(gè)為例進(jìn)行說(shuō)明。
- ambientOcclusion環(huán)境光遮擋
function updatePostProcess() {
const ambientOcclusion =
viewer.scene.postProcessStages.ambientOcclusion;
ambientOcclusion.enabled =
Boolean(viewModel.show) || Boolean(viewModel.ambientOcclusionOnly);
ambientOcclusion.uniforms.ambientOcclusionOnly = Boolean(
viewModel.ambientOcclusionOnly
);
ambientOcclusion.uniforms.intensity = Number(viewModel.intensity);
ambientOcclusion.uniforms.bias = Number(viewModel.bias);
ambientOcclusion.uniforms.lengthCap = Number(viewModel.lengthCap);
ambientOcclusion.uniforms.stepSize = Number(viewModel.stepSize);
ambientOcclusion.uniforms.blurStepSize = Number(
viewModel.blurStepSize
);
}

沒(méi)有開(kāi)啟AO效果如上圖一,開(kāi)啟AO效果如上圖二,單純的AO圖如上圖三
- bloom發(fā)光效果
function updatePostProcess() {
const bloom = viewer.scene.postProcessStages.bloom;
bloom.enabled = Boolean(viewModel.show);
bloom.uniforms.glowOnly = Boolean(viewModel.glowOnly);
bloom.uniforms.contrast = Number(viewModel.contrast);
bloom.uniforms.brightness = Number(viewModel.brightness);
bloom.uniforms.delta = Number(viewModel.delta);
bloom.uniforms.sigma = Number(viewModel.sigma);
bloom.uniforms.stepSize = Number(viewModel.stepSize);
}

(2)直接調(diào)用PostProcessStageLibrary中提供的方法去渲染場(chǎng)景特效
Cesium在場(chǎng)景處理庫(kù)中默認(rèn)為我們提供了如下8個(gè)效果,其實(shí)也是非常簡(jiǎn)單的,直接調(diào)用即可。
下面是一個(gè)切換動(dòng)畫(huà)小人效果的簡(jiǎn)單示例:
const stages = viewer.scene.postProcessStages;
const silhouette = stages.add(
Cesium.PostProcessStageLibrary.createSilhouetteStage()
);
const blackAndWhite = stages.add(
Cesium.PostProcessStageLibrary.createBlackAndWhiteStage()
);
const brightness = stages.add(
Cesium.PostProcessStageLibrary.createBrightnessStage()
);
const nightVision = stages.add(
Cesium.PostProcessStageLibrary.createNightVisionStage()
);
function updatePostProcess() {
silhouette.enabled = Boolean(viewModel.silhouette);
silhouette.uniforms.color = Cesium.Color.YELLOW;
blackAndWhite.enabled = Boolean(viewModel.blackAndWhiteShow);
blackAndWhite.uniforms.gradations = Number(
viewModel.blackAndWhiteGradations
);
brightness.enabled = Boolean(viewModel.brightnessShow);
brightness.uniforms.brightness = Number(viewModel.brightnessValue);
nightVision.enabled = Boolean(viewModel.nightVisionShow);
}

(3)編寫(xiě)自定義Shader實(shí)現(xiàn)場(chǎng)景特效
要想實(shí)現(xiàn)自定義Shader,不僅需要開(kāi)發(fā)者了解頂點(diǎn)著色器和片元著色器,openGL,還需要會(huì)編寫(xiě)GLSL(GL Shading Language)語(yǔ)言,通過(guò)自定義Shader可以表達(dá)更多的場(chǎng)景特效。關(guān)于GLSL編程語(yǔ)法,這里就不多贅述了,感興趣的可以查看其官網(wǎng)。下面是一個(gè)簡(jiǎn)單的給動(dòng)畫(huà)小人打馬賽克的示例:
const fragmentShaderSource = `
uniform sampler2D colorTexture;
varying vec2 v_textureCoordinates;
const int KERNEL_WIDTH = 16;
void main(void)
{
vec2 step = czm_pixelRatio / czm_viewport.zw;
vec2 integralPos = v_textureCoordinates - mod(v_textureCoordinates, 8.0 * step);
vec3 averageValue = vec3(0.0);
for (int i = 0; i < KERNEL_WIDTH; i++)
{
for (int j = 0; j < KERNEL_WIDTH; j++)
{
averageValue += texture2D(colorTexture, integralPos + step * vec2(i, j)).rgb;
}
}
averageValue /= float(KERNEL_WIDTH * KERNEL_WIDTH);
gl_FragColor = vec4(averageValue, 1.0);
}
`;
viewer.scene.postProcessStages.add(
new Cesium.PostProcessStage({
fragmentShader: fragmentShaderSource,
})
