引子
萬眾矚目的可視域分析功能終於來了!上一篇做這個預告的時候,壓根還沒開始碰這塊東西,還有點小忐忑呢,萬一弄不出來不就打臉了么,不過好在我臉轉的快沒打着。
預期效果

效果還可以吧,除了上面星星點點的小痘痘。
實現原理
ShadowMap
顧名思義,ShadowMap就是陰影貼圖。看Cesium的API,有一句話“Do not construct this directly”,看來官方是不希望我們動它,因為Cesium就是用它來實現陰影效果的。但是沒辦法,Cesium又不支持自定義光源,不動它我們就沒辦法實現可視域分析了(看過大佬們的博客,其實還是有其他方法的,不過這里暫不探討了)。下面我們看下它構造:
| 參數 | 類型 | 描述 | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
options |
Object | 子屬性:
|
通過上述構造參數可以很直觀地了解ShadowMap了,其中重點要講一下softShadows參數。描述中提到了percentage-closer-filtering,這個什么東東呢?等我從網上抄一段過來看看:Percentage Closer Filtering,簡稱PCF,是常用於柔化Shadow Map邊緣,產生軟陰影的一種技術,最早來源於1987年Computer Graphics上的論文,因為算法原理簡單,實現快速而且並不會占用太多運算,所以廣泛的應用於實時視頻游戲的陰影計算中。Shadow Map的一個比較明顯的缺點即是在生成的陰影邊緣鋸齒化很嚴重,而PCF則能有效地克服Shadow Map陰影邊緣的鋸齒。PCF通過在繪制陰影時,除了繪制該點陰影信息之外還對該點周圍陰影情況進行多次采樣並混合來實現鋸齒的柔化,這也是抗鋸齒最通用也是最簡易的處理方式。用過和我一樣方式實現可視域分析的童鞋都知道,鋸齒情況很感人,所以能改善鋸齒的一定要使用,當然僅靠這點還不夠,還得想些其他方法,比如優化着色器等。本篇用到的片元着色器代碼源於網絡,作者並沒有做太深的優化,我也是本着先用起來的態度拿過來了,等后期優化我會做些優化,到時候再更新文章吧。
Frustum
也就是視錐,它是相機的視覺表現效果。實際上視錐是不可見的,所以我們需要繪制出視錐的示意線,這里我們要使用FrustumOutlineGeometry來繪制,然后使用Primitive添加進來。下面來看一下FrustumOutlineGeometry的構造:
| 參數 | 類型 | 描述 | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
options |
Object | 子屬性:
|
我們看到第一個參數是frustum,也就是說要畫視錐的邊框線,還得先有視錐體才行。到了這里我們最直觀的想法就是缺啥造啥唄,的確我們可以使用PerspectiveFrustum構造一個視錐體出來。但是細細想來,我們好像搞反了,應該是先相機再有視錐最后才有示意線。那我們換個思路,既然構造ShadowMap需要一個Camera,那么我們就直接利用這個Camera,查看API后我們發現Camera有一個frustum屬性,可不,這就是視錐體啊。好了,視錐體有了,再為它附上起始點和方向,一個視錐示意線就搞定了。umn,那么相機怎么構造呢,相機中的視錐體又怎么設置呢?讓我們再研究一下相機。
Camera
老規矩,先查看API,發現Camera的構造參數只有一個scene,看來要我們的重心要放在它的屬性上了,我們挑幾個我們需要用到的幾個重點屬性了解一下,請看下表:
| 屬性 | 類型 | 默認值 | 描述 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
position |
Cartesian3 | 相機的起始點。 | |||||||||||||||||||||
direction |
Cartesian3 | 相機的方向。 | |||||||||||||||||||||
up |
Cartesian3 | 相機向上的方向。 | |||||||||||||||||||||
right |
Cartesian3 | 相機向右的方向。 | |||||||||||||||||||||
frustum |
PerspectiveFrustum | 子屬性:
|
看API中給出的例子:
1 // 創建一個沿負z軸向下的,位於原點的,視野為60度的,寬高比為1:1的相機。 2 var camera = new Cesium.Camera(scene); 3 camera.position = new Cesium.Cartesian3(); 4 camera.direction = Cesium.Cartesian3.negate(Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3()); 5 camera.up = Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_Y); 6 camera.frustum.fov = Cesium.Math.PI_OVER_THREE; 7 camera.frustum.near = 1.0; 8 camera.frustum.far = 2.0;
上述例子中給定了視錐的起點、向前的方向和向上的方向,就把視錐的方向確定下來了,當然你也可以使用setView方法,本篇例子正是使用這種方式。然后設置了視錐的視野角度、寬高比、近平面距離和遠平面距離,把視錐的形狀確定下來了。
視網
所謂的視網其實就是個網狀的視覺草圖示意線,沒什么用,就是讓它看起來又逼格一點。這里用的是EllipsoidGraphics繪制的實體對象,這個功能雖然比較簡單,但也有坑,至少你在網上看到的好些文章中都是有瑕疵的。那么照例先看API,結構如下:
| 參數 | 類型 | 描述 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
options |
Object | 子屬性:
|
看了API清晰明了,基本不用多說了。要注意的幾個屬性,innerRadii是橢球的內半徑,通俗點講就是把一個球的心給挖了,挖多大呢,就是它說了算,另外如果想顯示內切線條,這個值必須指定。當然最重要的四個屬性就是minimumClock、maximumClock、minimumCone、maximumCone了,它們的作用就是截取球面上的一塊補丁,讓它看起來像是視網。
具體實現
做完以上功課了,下面開始具體實現。
類封裝
1 // ViewShed.js
2
3 /**
4 * 可視域分析。
5 *
6 * @author Helsing
7 * @date 2020/08/28
8 * @alias ViewShedStage
9 * @class
10 * @param {Cesium.Viewer} viewer Cesium三維視窗。
11 * @param {Object} options 選項。
12 * @param {Cesium.Cartesian3} options.viewPosition 觀測點位置。
13 * @param {Cesium.Cartesian3} options.viewPositionEnd 最遠觀測點位置(如果設置了觀測距離,這個屬性可以不設置)。
14 * @param {Number} options.viewDistance 觀測距離(單位`米`,默認值100)。
15 * @param {Number} options.viewHeading 航向角(單位`度`,默認值0)。
16 * @param {Number} options.viewPitch 俯仰角(單位`度`,默認值0)。
17 * @param {Number} options.horizontalViewAngle 可視域水平夾角(單位`度`,默認值90)。
18 * @param {Number} options.verticalViewAngle 可視域垂直夾角(單位`度`,默認值60)。
19 * @param {Cesium.Color} options.visibleAreaColor 可視區域顏色(默認值`綠色`)。
20 * @param {Cesium.Color} options.invisibleAreaColor 不可視區域顏色(默認值`紅色`)。
21 * @param {Boolean} options.enabled 陰影貼圖是否可用。
22 * @param {Boolean} options.softShadows 是否啟用柔和陰影。
23 * @param {Boolean} options.size 每個陰影貼圖的大小。
24 */
25 class ViewShedStage {
26
27 constructor(viewer, options) {
28 this.viewer = viewer;
29 this.viewPosition = options.viewPosition;
30 this.viewPositionEnd = options.viewPositionEnd;
31 this.viewDistance = this.viewPositionEnd ? Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) : (options.viewDistance || 100.0);
32 this.viewHeading = this.viewPositionEnd ? getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0);
33 this.viewPitch = this.viewPositionEnd ? getPitch(this.viewPosition, this.viewPositionEnd) : (options.viewPitch || 0.0);
34 this.horizontalViewAngle = options.horizontalViewAngle || 90.0;
35 this.verticalViewAngle = options.verticalViewAngle || 60.0;
36 this.visibleAreaColor = options.visibleAreaColor || Cesium.Color.GREEN;
37 this.invisibleAreaColor = options.invisibleAreaColor || Cesium.Color.RED;
38 this.enabled = (typeof options.enabled === "boolean") ? options.enabled : true;
39 this.softShadows = (typeof options.softShadows === "boolean") ? options.softShadows : true;
40 this.size = options.size || 2048;
41
42 this.update();
43 }
44
45 add() {
46 this.createLightCamera();
47 this.createShadowMap();
48 this.createPostStage();
49 this.drawFrustumOutine();
50 this.drawSketch();
51 }
52
53 update() {
54 this.clear();
55 this.add();
56 }
57
58 clear() {
59 if (this.sketch) {
60 this.viewer.entities.removeById(this.sketch.id);
61 this.sketch = null;
62 }
63 if (this.frustumOutline) {
64 this.frustumOutline.destroy();
65 this.frustumOutline = null;
66 }
67 if (this.postStage) {
68 this.viewer.scene.postProcessStages.remove(this.postStage);
69 this.postStage = null;
70 }
71 }
72 }
73
74 export default ViewShed;
常規的ES6封裝和JSDoc注釋,無需多講。三個基本方法添加、更新、清除。
創建相機
1 createLightCamera() {
2 this.lightCamera = new Cesium.Camera(this.viewer.scene);
3 this.lightCamera.position = this.viewPosition;
4 // if (this.viewPositionEnd) {
5 // let direction = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(this.viewPositionEnd, this.viewPosition, new Cesium.Cartesian3()), new Cesium.Cartesian3());
6 // this.lightCamera.direction = direction; // direction是相機面向的方向
7 // }
8 this.lightCamera.frustum.near = this.viewDistance * 0.001;
9 this.lightCamera.frustum.far = this.viewDistance;
10 const hr = Cesium.Math.toRadians(this.horizontalViewAngle);
11 const vr = Cesium.Math.toRadians(this.verticalViewAngle);
12 const aspectRatio =
13 (this.viewDistance * Math.tan(hr / 2) * 2) /
14 (this.viewDistance * Math.tan(vr / 2) * 2);
15 this.lightCamera.frustum.aspectRatio = aspectRatio;
16 if (hr > vr) {
17 this.lightCamera.frustum.fov = hr;
18 } else {
19 this.lightCamera.frustum.fov = vr;
20 }
21 this.lightCamera.setView({
22 destination: this.viewPosition,
23 orientation: {
24 heading: Cesium.Math.toRadians(this.viewHeading || 0),
25 pitch: Cesium.Math.toRadians(this.viewPitch || 0),
26 roll: 0
27 }
28 });
29 }
上述采用了setView的方式確定相機的方向,你們可以看到我注釋的部分,是采用direction的方式。
創建陰影貼圖
1 createShadowMap() {
2 this.shadowMap = new Cesium.ShadowMap({
3 context: (this.viewer.scene).context,
4 lightCamera: this.lightCamera,
5 enabled: this.enabled,
6 isPointLight: true,
7 pointLightRadius: this.viewDistance,
8 cascadesEnabled: false,
9 size: this.size,
10 softShadows: this.softShadows,
11 normalOffset: false,
12 fromLightSource: false
13 });
14 this.viewer.scene.shadowMap = this.shadowMap;
15 }
這個沒什么好講的。
創建PostStage
1 createPostStage() {
2 const fs = glsl
3 const postStage = new Cesium.PostProcessStage({
4 fragmentShader: fs,
5 uniforms: {
6 shadowMap_textureCube: () => {
7 this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
8 return Reflect.get(this.shadowMap, "_shadowMapTexture");
9 },
10 shadowMap_matrix: () => {
11 this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
12 return Reflect.get(this.shadowMap, "_shadowMapMatrix");
13 },
14 shadowMap_lightPositionEC: () => {
15 this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
16 return Reflect.get(this.shadowMap, "_lightPositionEC");
17 },
18 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
19 this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
20 const bias = this.shadowMap._pointBias;
21 return Cesium.Cartesian4.fromElements(
22 bias.normalOffsetScale,
23 this.shadowMap._distance,
24 this.shadowMap.maximumDistance,
25 0.0,
26 new Cesium.Cartesian4()
27 );
28 },
29 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
30 this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
31 const bias = this.shadowMap._pointBias;
32 const scratchTexelStepSize = new Cesium.Cartesian2();
33 const texelStepSize = scratchTexelStepSize;
34 texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
35 texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;
36
37 return Cesium.Cartesian4.fromElements(
38 texelStepSize.x,
39 texelStepSize.y,
40 bias.depthBias,
41 bias.normalShadingSmooth,
42 new Cesium.Cartesian4()
43 );
44 },
45 camera_projection_matrix: this.lightCamera.frustum.projectionMatrix,
46 camera_view_matrix: this.lightCamera.viewMatrix,
47 helsing_viewDistance: () => {
48 return this.viewDistance;
49 },
50 helsing_visibleAreaColor: this.visibleAreaColor,
51 helsing_invisibleAreaColor: this.invisibleAreaColor,
52 }
53 });
54 this.postStage = this.viewer.scene.postProcessStages.add(postStage);
55 }
創建視錐線
1 drawFrustumOutline() {
2 const scratchRight = new Cesium.Cartesian3();
3 const scratchRotation = new Cesium.Matrix3();
4 const scratchOrientation = new Cesium.Quaternion();
5 const position = this.lightCamera.positionWC;
6 const direction = this.lightCamera.directionWC;
7 const up = this.lightCamera.upWC;
8 let right = this.lightCamera.rightWC;
9 right = Cesium.Cartesian3.negate(right, scratchRight);
10 let rotation = scratchRotation;
11 Cesium.Matrix3.setColumn(rotation, 0, right, rotation);
12 Cesium.Matrix3.setColumn(rotation, 1, up, rotation);
13 Cesium.Matrix3.setColumn(rotation, 2, direction, rotation);
14 let orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation);
15
16 let instance = new Cesium.GeometryInstance({
17 geometry: new Cesium.FrustumOutlineGeometry({
18 frustum: this.lightCamera.frustum,
19 origin: this.viewPosition,
20 orientation: orientation
21 }),
22 id: Math.random().toString(36).substr(2),
23 attributes: {
24 color: Cesium.ColorGeometryInstanceAttribute.fromColor(
25 Cesium.Color.YELLOWGREEN//new Cesium.Color(0.0, 1.0, 0.0, 1.0)
26 ),
27 show: new Cesium.ShowGeometryInstanceAttribute(true)
28 }
29 });
30
31 this.frustumOutline = this.viewer.scene.primitives.add(
32 new Cesium.Primitive({
33 geometryInstances: [instance],
34 appearance: new Cesium.PerInstanceColorAppearance({
35 flat: true,
36 translucent: false
37 })
38 })
39 );
40 }
上面有可能碰到的坑是最后添加Primitive的時候報錯,可嘗試appearance的flat屬性設置為true,默認值為false。
創建視網
1 drawSketch() {
2 this.sketch = this.viewer.entities.add({
3 name: 'sketch',
4 position: this.viewPosition,
5 orientation: Cesium.Transforms.headingPitchRollQuaternion(
6 this.viewPosition,
7 Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - this.horizontalViewAngle, this.viewPitch, 0.0)
8 ),
9 ellipsoid: {
10 radii: new Cesium.Cartesian3(
11 this.viewDistance,
12 this.viewDistance,
13 this.viewDistance
14 ),
15 // innerRadii: new Cesium.Cartesian3(2.0, 2.0, 2.0),
16 minimumClock: Cesium.Math.toRadians(-this.horizontalViewAngle / 2),
17 maximumClock: Cesium.Math.toRadians(this.horizontalViewAngle / 2),
18 minimumCone: Cesium.Math.toRadians(this.verticalViewAngle + 7.75),
19 maximumCone: Cesium.Math.toRadians(180 - this.verticalViewAngle - 7.75),
20 fill: false,
21 outline: true,
22 subdivisions: 256,
23 stackPartitions: 64,
24 slicePartitions: 64,
25 outlineColor: Cesium.Color.YELLOWGREEN
26 }
27 });
28 }
上述注釋的代碼是內半徑,設置之后可以顯示球的中心向球面的發射線。
另外
1 function getHeading(fromPosition, toPosition) {
2 let finalPosition = new Cesium.Cartesian3();
3 let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
4 Cesium.Matrix4.inverse(matrix4, matrix4);
5 Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
6 Cesium.Cartesian3.normalize(finalPosition, finalPosition);
7 return Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
8 }
9
10 function getPitch(fromPosition, toPosition) {
11 let finalPosition = new Cesium.Cartesian3();
12 let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
13 Cesium.Matrix4.inverse(matrix4, matrix4);
14 Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
15 Cesium.Cartesian3.normalize(finalPosition, finalPosition);
16 return Cesium.Math.toDegrees(Math.asin(finalPosition.z));
17 }
上述兩個方法是獲取偏航角和俯仰角。額,你發現了,兩個方法怎么那么像啊,我懶得整合了,你們看着辦吧。
下面后處理中的GLSL代碼是借鑒了網友的思路,核心代碼挺簡單的,看下注釋就理解了,不做贅述了。
1 export default `
2 #define USE_CUBE_MAP_SHADOW true
3 uniform sampler2D colorTexture;
4 uniform sampler2D depthTexture;
5 varying vec2 v_textureCoordinates;
6 uniform mat4 camera_projection_matrix;
7 uniform mat4 camera_view_matrix;
8 uniform samplerCube shadowMap_textureCube;
9 uniform mat4 shadowMap_matrix;
10 uniform vec4 shadowMap_lightPositionEC;
11 uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
12 uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
13 uniform float helsing_viewDistance;
14 uniform vec4 helsing_visibleAreaColor;
15 uniform vec4 helsing_invisibleAreaColor;
16
17 struct zx_shadowParameters
18 {
19 vec3 texCoords;
20 float depthBias;
21 float depth;
22 float nDotL;
23 vec2 texelStepSize;
24 float normalShadingSmooth;
25 float darkness;
26 };
27
28 float czm_shadowVisibility(samplerCube shadowMap, zx_shadowParameters shadowParameters)
29 {
30 float depthBias = shadowParameters.depthBias;
31 float depth = shadowParameters.depth;
32 float nDotL = shadowParameters.nDotL;
33 float normalShadingSmooth = shadowParameters.normalShadingSmooth;
34 float darkness = shadowParameters.darkness;
35 vec3 uvw = shadowParameters.texCoords;
36 depth -= depthBias;
37 float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
38 return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
39 }
40
41 vec4 getPositionEC(){
42 return czm_windowToEyeCoordinates(gl_FragCoord);
43 }
44
45 vec3 getNormalEC(){
46 return vec3(1.);
47 }
48
49 vec4 toEye(in vec2 uv,in float depth){
50 vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
51 vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
52 posInCamera=posInCamera/posInCamera.w;
53 return posInCamera;
54 }
55
56 vec3 pointProjectOnPlane(in vec3 planeNormal,in vec3 planeOrigin,in vec3 point){
57 vec3 v01=point-planeOrigin;
58 float d=dot(planeNormal,v01);
59 return(point-planeNormal*d);
60 }
61
62 float getDepth(in vec4 depth){
63 float z_window=czm_unpackDepth(depth);
64 z_window=czm_reverseLogDepth(z_window);
65 float n_range=czm_depthRange.near;
66 float f_range=czm_depthRange.far;
67 return(2.*z_window-n_range-f_range)/(f_range-n_range);
68 }
69
70 float shadow(in vec4 positionEC){
71 vec3 normalEC=getNormalEC();
72 zx_shadowParameters shadowParameters;
73 shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
74 shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
75 shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
76 shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
77 vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
78 float distance=length(directionEC);
79 directionEC=normalize(directionEC);
80 float radius=shadowMap_lightPositionEC.w;
81 if(distance>radius)
82 {
83 return 2.0;
84 }
85 vec3 directionWC=czm_inverseViewRotation*directionEC;
86 shadowParameters.depth=distance/radius-0.0003;
87 shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
88 shadowParameters.texCoords=directionWC;
89 float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
90 return visibility;
91 }
92
93 bool visible(in vec4 result)
94 {
95 result.x/=result.w;
96 result.y/=result.w;
97 result.z/=result.w;
98 return result.x>=-1.&&result.x<=1.
99 &&result.y>=-1.&&result.y<=1.
100 &&result.z>=-1.&&result.z<=1.;
101 }
102
103 void main(){
104 // 釉色 = 結構二維(顏色紋理, 紋理坐標)
105 gl_FragColor = texture2D(colorTexture, v_textureCoordinates);
106 // 深度 = 獲取深度(結構二維(深度紋理, 紋理坐標))
107 float depth = getDepth(texture2D(depthTexture, v_textureCoordinates));
108 // 視角 = (紋理坐標, 深度)
109 vec4 viewPos = toEye(v_textureCoordinates, depth);
110 // 世界坐標
111 vec4 wordPos = czm_inverseView * viewPos;
112 // 虛擬相機中坐標
113 vec4 vcPos = camera_view_matrix * wordPos;
114 float near = .001 * helsing_viewDistance;
115 float dis = length(vcPos.xyz);
116 if(dis > near && dis < helsing_viewDistance){
117 // 透視投影
118 vec4 posInEye = camera_projection_matrix * vcPos;
119 // 可視區顏色
120 // vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
121 // vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
122 if(visible(posInEye)){
123 float vis = shadow(viewPos);
124 if(vis > 0.3){
125 gl_FragColor = mix(gl_FragColor,helsing_visibleAreaColor,.5);
126 } else{
127 gl_FragColor = mix(gl_FragColor,helsing_invisibleAreaColor,.5);
128 }
129 }
130 }
131 }`;
小結
沒啥好總結的,實在沒研究個啥出來。不過這個東西以前沒搞過,倉促的做出來,僅限於從無到有吧,待日后深入研究吧。差點忘記說了,使用后處理的方式做的可視域分析,建議你在使用的時候開啟深度檢測和對數深度,否則效果出不來。

