使用着色器在WebGL3D場景中呈現行星表面地形

實驗目的:按照必定規律生成類地行星地表地形區塊,並用合理的方式將地形塊顯示出來css

涉及知識:Babylon.js引擎應用、着色器編程、正態分佈、數據處理、canvas像素操做html

github地址:https://github.com/ljzc002/ljzc002.github.io/tree/master/DataWarhtml5

1、在球體網格上顯示紋理的傳統方法:python

一、常見的一種星球表面繪製方法是這樣的:git

首先用三角形近似的模擬一個球體網格:github

這個簡單場景的代碼以下:web

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>演示球體的繪製</title>
 6     <link href="../../CSS/newland.css" rel="stylesheet">
 7     <link href="../../CSS/stat.css" rel="stylesheet">
 8     <script src="../../JS/LIB/babylon.32.all.max.js"></script>
 9     <script src="../../JS/LIB/stat.js"></script>
10 </head>
11 <body>
12 <div id="div_allbase">
13     <canvas id="renderCanvas"></canvas>
14     <div id="fps" style="z-index: 301;"></div>
15 </div>
16 </body>
17 <script>
18     var canvas,engine,scene,gl;
19     canvas = document.getElementById("renderCanvas");
20     engine = new BABYLON.Engine(canvas, true);
21     gl=engine._gl;//決定在這裏結合使用原生OpenGL和Babylon.js;
22     scene = new BABYLON.Scene(engine);
23     var divFps = document.getElementById("fps");
24     //全局對象
25     var light0//全局光源
26             ,camera0//主相機
27             ;
28     window.onload=webGLStart;
29     window.addEventListener("resize", function () {
30         engine.resize();
31     });
32     function webGLStart()
33     {
34         gl=engine._gl;
35         createScene();
36         MyBeforeRender();
37     }
38     var createScene = function (engine) {
39         camera0 =new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, -20), scene);
40         camera0.attachControl(canvas, true);
41         camera0.speed=0.5;
42         camera0.minZ=0.0001;
43         light0 = new BABYLON.HemisphericLight("Hemi0", new BABYLON.Vector3(0, 1, 0), scene);
44         sphere1=BABYLON.MeshBuilder.CreateSphere("sphere1",{segments:10,diameter:10.0},scene);
45         var groundMaterial = new BABYLON.StandardMaterial("groundMat", scene);
46         groundMaterial.wireframe=true;
47         sphere1.material=groundMaterial;
48 
49     }
50     function MyBeforeRender()
51     {
52         scene.registerBeforeRender(function() {
53             if(scene.isReady())
54             {
55 
56             }
57         });
58         engine.runRenderLoop(function () {
59             engine.hideLoadingUI();
60             if (divFps) {
61                 // Fps
62                 divFps.innerHTML = engine.getFps().toFixed() + " fps";
63             }
64             scene.render();
65         });
66 
67     }
68 </script>
69 </html>
View Code

而後將一張紋理貼圖的紋理座標對應到球體網格中的每一個三角形上,具體原理以下:ajax

以上內容引用自吳亞峯著《OpenGLES3.x遊戲開發》,Babylon.js中的紋理對應規則能夠參考https://www.cnblogs.com/ljzc002/p/6884252.html中的代碼。算法

可是這種繪製方式存在如下幾個缺點:sql

a、爲了將二維的圖片映射到三維的球面上,圖片或者紋理座標必須通過複雜的「拓撲變換」(好比圖中的南極洲明顯通過了拉伸變換),這致使咱們在行星表麪點擊一個點時,很難直觀的將它對應到圖片上的某個像素,同時生成適合球面的圖片也須要使用專門的工具進行計算。

b、若是把每一個三角形做爲一個可交互對象,極地區域的可交互對象將過於密集,想象一個回合制戰棋遊戲,玩家會發現極地區域的格子太密而赤道附近的格子太稀疏。

c、在畫面拉近時紋理貼圖會由於信息不足出現不受控制的模糊或變形,固然,咱們能夠在視角拉近時用更多的細節貼圖來提供更多的信息,但那就是一個更浩大的工程了。

爲了避開上述缺點,我決定採用另外一種球體紋理繪製方式。

2、使用自定義着色器繪製自定義紋理:

一、在Babylon.js引擎中使用自定義着色器:

Babylon.js經過「着色器材質」對象提供對自定義着色器的支持:

 1 var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene,{
 2                         vertexElement: "sh1v4.sh",
 3                         fragmentElement: "sh1f4.sh",
 4                     },
 5                     {
 6                         attributes: ["position"],
 7                         uniforms: ["worldViewProjection","worldView"]
 8                     });
 9             amigaMaterial.setVector4("uColor", new BABYLON.Vector4(0.0,1.0,0.0,1.0));
10             sphere1.material=amigaMaterial;

其中ShaderMaterial構造方法的第一個參數是材質名稱、第二個參數是場景對象、第三個參數標明瞭頂點着色器和片元着色器的文件名稱,參考Babylon.js源碼能夠看到引擎支持的幾種着色器代碼對象命名方式:

 1 Effect.prototype._loadFragmentShader = function (fragment, callback) {
 2             // DOM element ?着色器代碼是DOM標籤中的內容
 3             if (fragment instanceof HTMLElement) {
 4                 var fragmentCode = BABYLON.Tools.GetDOMTextContent(fragment);
 5                 callback(fragmentCode);
 6                 return;
 7             }
 8             // Base64 encoded ?着色器代碼使用了base64編碼
 9             if (fragment.substr(0, 7) === "base64:") {
10                 var fragmentBinary = window.atob(fragment.substr(7));
11                 callback(fragmentBinary);
12                 return;
13             }
14             // Is in local store ?着色器代碼在Babylon.js自帶的着色器代碼庫裏
15             if (Effect.ShadersStore[fragment + "PixelShader"]) {
16                 callback(Effect.ShadersStore[fragment + "PixelShader"]);
17                 return;
18             }
19             if (Effect.ShadersStore[fragment + "FragmentShader"]) {
20                 callback(Effect.ShadersStore[fragment + "FragmentShader"]);
21                 return;
22             }
23             var fragmentShaderUrl;//着色器代碼是一個單獨的文件,須要經過Ajax加載
24             if (fragment[0] === "." || fragment[0] === "/" || fragment.indexOf("http") > -1) {
25                 fragmentShaderUrl = fragment;
26             }
27             else {//默認狀況下Engine.ShadersRepository = "src/Shaders/";
28                 fragmentShaderUrl = BABYLON.Engine.ShadersRepository + fragment;
29             }
30             // Fragment shader
31             BABYLON.Tools.LoadFile(fragmentShaderUrl + ".fragment.fx", callback);
32         };

我選擇了最後一種方式,將着色器文件放在/src/Shaders/下面,不要忘記給着色器文件添加後綴:

 第四個參數是須要Babylon.js從內存向顯卡傳遞的「默認」變量,其中attributes裏是Babylon.js的各類默認的頂點數據,能夠選擇Mesh.geometry._vertexBuffers裏的如下幾種頂點數據傳給着色器:

這裏我只選擇了將每一個頂點的位置傳遞給着色器,Babylon.js引擎替咱們進行了編譯連接着色器程序、綁定緩存等一系列操做(Babylon.js中以「_」開頭的變量通常都是在渲染過程當中創建的,只有在渲染開始後纔有值)。

假設一個網格有1000個頂點,那麼這1000個頂點的位置數據將被分別發送到顯卡上的1000個頂點着色器中,每一個着色器使用收到的頂點數據進行計算。

uniforms裏是Babylon.js向顯卡發送的默認通用變量,其中world對應網格的變換矩陣,View是相機的變換矩陣,Projection是相機的投影矩陣,worldViewProjection是三個矩陣變換的合併(關於矩陣變換能夠參考https://www.cnblogs.com/ljzc002/p/8927221.html中的介紹,或者查看我在B站上傳的3D編程入門視頻教程https://space.bilibili.com/25346426/#/)

對於全部的着色器uniforms型數據都是通用的,好比上面提到的1000個頂點着色器都會使用相同的"worldViewProjection"和"worldView"變量。attributes和uniforms都屬於OpenGL的「存儲限定符」。

第九行代碼設定了一個非默認的uniforms型變量,第十行將這個材質交給球體網格。

二、WebGL版本選擇:

在進行glsl編程以前,一個重要的步驟是選擇要使用的WebGL版本:

OpenGL發展歷史以下:(源文件地址:https://docs.qq.com/sheet/B8uRgG1gE9T32RzNoW38xEnX2epfOY1cwvqG3)

可見WebGL1.0對應早期的OpenGL2.x,WebGL2.0對應較新的OpenGL4.x,顯然WebGL2.0的功能更爲強大,但考慮到個人筆記本顯卡不支持WebGL2.0,只好使用舊的WebGL1.0。本文後面的glsl編程均使用OpenGL2.0的語法,OpenGL2.0存在不少缺陷,因此後面的部份內容也正是爲了解決這些缺陷而編寫的。

Babylon.js能夠自動檢測電腦支持的WebGL版本,並優先使用最新版:

 1 // GL
 2             if (!options.disableWebGL2Support) {
 3                 try {
 4                     this._gl = (canvas.getContext("webgl2", options) || canvas.getContext("experimental-webgl2", options));
 5                     if (this._gl) {
 6                         this._webGLVersion = 2.0;
 7                     }
 8                 }
 9                 catch (e) {
10                     // Do nothing
11                 }
12             }
13             if (!this._gl) {
14                 if (!canvas) {
15                     throw new Error("The provided canvas is null or undefined.");
16                 }
17                 try {
18                     this._gl = (canvas.getContext("webgl", options) || canvas.getContext("experimental-webgl", options));
19                 }
20                 catch (e) {
21                     throw new Error("WebGL not supported");
22                 }
23             }
24             if (!this._gl) {
25                 throw new Error("WebGL not supported");
26             }

Babylon.js源碼裏包括不少實用的3D編程工具,即便不使用Babylon.js引擎也可使用其中的工具簡化原生WebGL開發。

三、簡單glsl代碼:

 測試用的頂點着色器代碼:

 1 uniform mat4 worldViewProjection;
 2 uniform mat4 worldView;
 3 attribute vec3 position;
 4 
 5 varying vec3 vPosition;
 6 
 7 void main(){
 8     gl_Position=worldViewProjection*vec4(position,1);    
 9     vPosition=vec3(worldView*vec4(position,1));
10 }

這裏varying是WebGL1.0中的第三種存儲限定符,它表示這個變量是頂點着色器的計算結果,通過插值後傳入片元着色器(關於WegGL1.0基礎知識,推薦觀看我之前發佈的3D編程入門教程)

gl_Position是OpenGL的內置變量,表示這個頂點通過各類矩陣變換以後在視口中渲染的位置,vPositon指頂點相對於相機的位置。

須要注意的是:除構造函數外,glsl不支持浮點數和整形數之間的自動轉換,浮點數經過「int i=int(f)」轉爲整形數,整形數經過"float f=float(i)"轉換爲浮點數,上述代碼中的vec4()和vec3()則分別是四元浮點數組和三元浮點數組的構造函數,另外WebGL1.0不具有內置的四捨五入函數,須要使用「floor(f+0.5)」代替四捨五入,而且四捨五入以後仍然是浮點數而非整形數。

除了數組的索引外,着色器中絕大部分的計算都是浮點計算,而將整形計算的結果做爲數組索引時也會遇到問題,後面會詳細討論如何處理這一問題。

片元着色器代碼:

 1 precision mediump float;
 2 varying vec3 vPosition;
 3 uniform vec4 uColor;
 4 void main()
 5 {
 6     vec4 tempColor=uColor;
 7     //對2取模,參數必須是浮點型
 8     if(mod((vPosition.x+vPosition.y+vPosition.z),2.0)>1.0)
 9     {
10         tempColor+=vec4(0.0,-0.4,0.0,0.0);
11     }
12     gl_FragColor=tempColor;
13 }

gl_FragColor是一個內置變量,表示片元的最終顏色,注意glsl中的顏色值從0.0到1.0,而不是html中的0到255。

執行代碼效果以下:

可見,隨着相機的移動,球體的紋理自動發生變化,這類效果是很難用貼圖方式實現的。

3、生成並保存簡單的棋盤地形

假設行星的周長爲40000km,將每一個地塊設爲長寬均爲100km的正方形,生成並保存一個包含50000多個地塊的棋盤型地面:

一、數據保存:

考慮到每一個地塊都要具備獨立的交互能力,使用文件方式保存效率極低,嘗試了html5的本地存儲功能,發現Chrome瀏覽器的本地存儲空間只有5M,難以支持計劃中的對多個行星數據的保存,最終決定使用h2微型數據庫保存地塊數據(讀者能夠本身搜索關於h2數據庫的知識,個人視頻教程裏也提到了部分相關知識)。

a、在數據庫中建表:

將行星想象爲一個一半在地上一半在地下的建築,不一樣的緯度對應了不一樣的層數,每一層有若干個大小相同的房間

 1 --創建地區塊表
 2 create table tab_dqk (
 3 ID varchar(40) NOT NULL,
 4 planetid varchar(40),
 5 beta double,
 6 pbeta double,
 7 alpha double,
 8 palpha double,
 9 weight varchar(1000)
10 );
11 comment on table tab_dqk is '地區塊表';
12 comment on column tab_dqk.id is '主鍵ID';
13 comment on column tab_dqk.planetid is '地區塊所屬的行星id';
14 comment on column tab_dqk.beta is '地區塊的仰角';
15 comment on column tab_dqk.pbeta is '地區塊仰角的區分度';--即這個beta仰角上下pbeta弧度都屬於這一層
16 comment on column tab_dqk.alpha is '地區塊水平轉角';
17 comment on column tab_dqk.palpha is '地區塊水平轉角的區分度';--即這個alpha水平轉角左右palpha弧度都屬於這個房間
18 comment on column tab_dqk.weight is '用JSON表示的地形類型id權重';
19 
20 alter table tab_dqk add column floor int;
21 alter table tab_dqk add column room int;
22 alter table tab_dqk add column altitude double;
23 
24 comment on column tab_dqk.floor is '地區塊位於第幾層';
25 comment on column tab_dqk.room is '地區塊位於這一層的第幾個房間';
26 comment on column tab_dqk.altitude is '地區塊的海拔高度';
27 comment on column tab_dqk.weight is '地區塊類型';
 1 --創建行星表
 2 create table tab_planet
 3 (
 4 id varchar(40) NOT NULL,
 5 name varchar(20) NOT NULL,
 6 coreid varchar(40),
 7 min_floor int NOT NULL,
 8 max_floor int NOT NULL,
 9 width_room int NOT NULL,
10 radius double,
11 mass double,
12 gravity double,
13 orbit double,
14 cycle double
15 );
16 comment on table tab_planet is '行星表';
17 comment on column tab_planet.id is '主鍵ID';
18 comment on column tab_planet.name is '行星名字';
19 comment on column tab_planet.coreid is '圍繞旋轉的主星id';
20 comment on column tab_planet.min_floor is '最低層數';
21 comment on column tab_planet.max_floor is '最高層數';
22 comment on column tab_planet.width_room is '數據寬度';
23 comment on column tab_planet.radius is '半徑(km)';
24 comment on column tab_planet.mass is '質量(t)';
25 comment on column tab_planet.gravity is '重力加速度';
26 comment on column tab_planet.orbit is '同步軌道高度';
27 comment on column tab_planet.cycle is '自轉週期';
28 
29 alter table tab_planet add column perimeter int;
30 
31 comment on column tab_planet.perimeter is '行星周長';

b、dao實現

傳統MVC思想認爲瀏覽器端的安全沒有保證,必須在瀏覽器和數據庫之間加入一種「後臺程序」來提升安全性,這種程序一般由JAVA、C#實現,近些年也出現了許多由python和JavaScript實現的後臺程序。這些後臺程序通常包括三層:負責接收瀏覽器訪問的service層、負責將特定訪問參數和數據關聯起來的application層,負責訪問數據庫的dao層。

可是我認爲這個實驗中的全部參與者都是可信任的,因此爲了程序的簡潔要嘗試去掉後臺程序,我發現h2數據庫服務支持http協議通訊,經過使用Fiddler對h2的網頁控制檯進行抓包,編寫了直接用瀏覽器和數據庫通訊的代碼:(代碼在Linkh2.js中)

 1 /**
 2  * Created by lz on 2018/5/15.
 3  */
 4 var jsessionid="";
 5 var Url="";
 6 var UrlHead="http://127.0.0.1:8082/";
 7 var H2State="offline";
 8 var H2LoginCallback;//回調函數對象
 9 function H2Login(func)
10 {
11     H2LoginCallback=func;
12     Url=UrlHead+"";
13     Argv="";
14     Request(xmlHttp,"POST",Url,true,Argv,"application/x-www-form-urlencoded",H2LoginCallBack,0);
15 }
16 function H2LoginCallBack()
17 {
18     if(xmlHttp.readyState==4) {
19         if(isTimout=="1")
20         {
21             alert("登錄驗證請求超時!!");
22             clearTimeout(timer);
23             xmlHttp.abort();
24         }
25         else {
26             if (xmlHttp.status == 200) {
27                 clearTimeout(timer);//中止定時器
28                 try
29                 {
30                     var str_id=xmlHttp.responseText;
31                     xmlHttp.abort();
32                     jsessionid=str_id.substr(str_id.search(/jsessionid/)+11,32) ;//從h2獲取一個jsessionid
33                     console.log(jsessionid);
34                     H2Login2();
35                 }catch(e)
36                 {
37                     alert(e);
38                     console.error(e)
39                     xmlHttp.abort();
40                 }
41             }
42         }
43     }
44 }
45 function H2Login2()
46 {
47     Url=UrlHead+"login.do?jsessionid="+jsessionid;//用這個jsessionid登陸
48     Argv="language=en&setting=Generic H2 (Embedded)&name=Generic H2 (Embedded)" +
49         "&driver=org.h2.Driver&url=jdbc:h2:tcp://127.0.0.1/../../datawar" +
50         "&user=datawar&password=datawar";
51     Request(xmlHttp,"POST",Url,true,Argv,"application/x-www-form-urlencoded",H2Login2CallBack,0);
52 }
53 function H2Login2CallBack()
54 {
55     if(xmlHttp.readyState==4) {
56         if(isTimout=="1")
57         {
58             alert("登錄驗證請求超時!!");
59             clearTimeout(timer);
60             xmlHttp.abort();
61         }
62         else {
63             if (xmlHttp.status == 200) {
64                 clearTimeout(timer);//中止定時器
65                 try
66                 {
67                     var str_logres=xmlHttp.responseText;//這時已經在h2服務端創建登陸狀態
68                     xmlHttp.abort();
69                     console.log("完成h2數據庫登陸");
70                     H2State="online";
71                     //Query();
72                     //CreateChess();//測試時將運算啓動放在這裏,實際使用時,經過渲染循環檢測H2State標誌來啓動運算
73                     H2LoginCallback();//這樣能夠執行函數對象嗎????《-能夠
74                 }catch(e)
75                 {
76                     alert(e);
77                     console.error(e)
78                     xmlHttp.abort();
79                 }
80             }
81         }
82     }
83 }

其中「Request」是一個Ajax請求函數,內容以下:

 1 /**
 2  * Created by Administrator on 2015/1/28.
 3  * Update by Administrator on 2015/09/17.
 4  */
 5 //Ajax通訊頁面
 6 var xmlHttp=createXMLHttpRequest();
 7 var isTimout="0";//0表示未超時
 8 var timer;//用來存定時器
 9 function createXMLHttpRequest()
10 {
11     var xhr=false;
12     if(window.XMLHttpRequest)
13     {
14         try
15         {
16             netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
17         }
18         catch(e)
19         {
20 
21         }
22         xhr=new XMLHttpRequest();
23     }
24     else if(window.ActiveXObject)
25     {
26         try
27         {
28             xhr=new window.ActiveXObject("Msxm12.XMLHTTP");
29 
30         }
31         catch(e)
32         {
33             try
34             {
35                 xhr=new window.ActiveXObject("Microsoft.XMLHTTP");
36             }
37             catch(e)
38             {
39 
40                 alert("您的瀏覽器不支持ajax!");
41 
42             }
43         }
44     }
45     return xhr;
46 }
47 //目前的版本爲單線程的ajax訪問,不能支持多個ajax同時訪問
48 Request=function(xhr,method,src,isajax,argv,content_type,recallfunc,timeout)
49 {//鏈接對象、鏈接方式、鏈接目的地址、是否異步、提交內容、表單的內容類型、回調函數、 超時時間
50     xmlHttp.open(method,src,isajax)//第三個參數爲true爲異步方式
51     if(method=="POST")
52         xmlHttp.setRequestHeader("Content-Type",content_type);
53     xmlHttp.setRequestHeader("Access-Control-Allow-Origin","*");
54     xmlHttp.onreadystatechange=recallfunc;
55     xmlHttp.send(argv);
56     isTimout="0";
57     if(timeout==1) {//0表示不檢測超時,1表示檢測超時,好像很差使??
58         timer = window.setTimeout("dualTimeout();", timeout);
59     }
60 }
View Code

可見瀏覽器直接使用明文傳遞了數據庫用戶名和密碼

二、數據生成

代碼以下:(testchess.html)

 1 //根據極座標和變化的半徑生成顏色交錯的地區塊
 2     function CreateChess()
 3     {
 4         var size_dqk=100;//每一個地區塊的長寬都是100km
 5         var perimeter_planet=40000;//這個行星的周長是40000km
 6         var r_planet=perimeter_planet/(2*Math.PI);//行星的半徑
 7      
 8 
 9         //咱們將行星的表面想象成一個一半在地下一半在地上的建築,len_beta就是根據周長算得的地上或地下的層數
10         var len_beta=sswr(((perimeter_planet/2)/size_dqk)/2);//經過長度而不是弧度來分層!!
11         var pbeta=(Math.PI/4)/len_beta;
12 
13         //對於每一層,
14         for(var i=-len_beta;i<=len_beta;i++)
15         {
16             var rad_beta=(Math.PI/2)*(i/len_beta);
17             var r_floor=Math.cos(rad_beta)*r_planet;//這一層的半徑
18             var len_alpha_floor=sswr((r_floor*2*Math.PI)/size_dqk);//根據這一層的周長算出這一層有多少個房間
19             var palpha=Math.PI/len_alpha_floor;//每個地區塊的角度邊界,在這個邊界範圍內即屬於這個地區塊
20             var beta=i*pbeta;
21             console.log(i+"/"+len_beta+">"+len_alpha_floor);
22             //對於圓環上的每個片
23             for(var j=0;j<len_alpha_floor;j++)
24             {
25                 var obj={};
26                 obj.palpha=palpha;
27                 obj.alpha=j*palpha;
28                 obj.pbeta=pbeta;
29                 obj.beta=beta;
30                 obj.weight={};
31                 obj.floor=i;
32                 obj.room=j;
33                 if((Math.floor(i/3)%2)==(Math.floor(j/3)%2))//棋盤形,但試驗時並無生成想要的棋盤形,沒有調試是爲何
34                 {
35                     obj.weight="{land_textblack:1}";//在數據庫中直接使用字符串形式保存
36                 }
37                 else
38                 {
39                     obj.weight="{land_textyellow:1}";
40                 }
41 
42                 //先嚐試單獨推入每個地區塊
43                 PushChess(obj);//將這個地區塊數據寫入數據庫
44             }
45         }
46     }
47     function PushChess(obj)
48     {
49         Url="http://127.0.0.1:8082/query.do?jsessionid="+jsessionid;
50         Argv="sql=insert into tab_dqk values(uuid(),'test',"+obj.beta+","+obj.pbeta+","+obj.alpha+","+obj.palpha+",'"
51                 +obj.weight+"',"+obj.floor+","+obj.room+")";
52         //使用同步Ajax請求保證進度同步,在連續使用同步Ajax時不須要xmlHttp.abort()!!!!
53         Request(xmlHttp,"POST",Url,false,Argv,"application/x-www-form-urlencoded",PushChessCallBack,0);
54     }
55     function PushChessCallBack()
56     {//空方法
57 
58     }

半徑計算圖解以下:

代碼中sswr是本身編寫的一個四捨五入方法:

 1 //四捨五入,目標浮點數、取整方式,小數點後精度
 2 function sswr(float,type,accuracy)
 3 {
 4     var float2=float;
 5     if(type==null||type==0)
 6     {
 7 
 8     }
 9     else if(type==1)//向下取整
10     {
11         float2+=-0.5;
12     }
13     else if(type==2)//向上取整
14     {
15         float2+=0.5;
16     }
17     var acc=Math.pow(10,accuracy?accuracy:0);//用於保留小數點後精度,保留小數點後一位時,acc爲10
18     var int_res=div(Math.round(float2*acc),acc);
19     return int_res;
20 }
View Code

爲了編程簡單,生成每一個地區塊後馬上用同步方式將數據存入數據庫,等保存完畢後再生成下一個地區塊,這個簡易的Ajax也只支持單線程工做。奇怪的是此次測試時使用post方式成功保存數據,但後面編程時post方式則無Ajax返回值,改用get方式傳遞參數後又保存成功,由於時間有限沒有詳細研究,讀者若是感興趣能夠好好調試一下,告訴我爲何會這樣。

最終在數據庫中插入了50930條數據。

三、數據顯示

數據查詢和顯示代碼以下:(testdatatexture.html)

a、查詢行星信息:

 1 function CreateChess()//讀取地區塊的一些總體信息,從planet表獲取更合理?
 2     {
 3         Url="http://127.0.0.1:8082/query.do?jsessionid="+jsessionid;
 4         Argv="sql=select min_floor,max_floor,width_room from tab_planet where id='test'";//查找這個行星的地區塊層數
 5         //使用同步Ajax請求保證進度同步
 6         Request(xmlHttp,"POST",Url,true,Argv,"application/x-www-form-urlencoded",CreateChessCallBack,0);
 7     }
 8     //地區塊計算使用的變量
 9     var min_floor= 0,max_floor= 0,width_room= 0,height_floor=0;//最低層數,最高層數
10     var can_temp=document.createElement("canvas");//用來作像素處理的隱形canvas
11     var context_temp;
12     var imagedata_temp;//=context_temp.getImageData(0,0,400,199);
13     var strsrc_dqk="";

數據中包含了每一個地區塊的「層數」和「房間號」信息,個人思路是把這些信息傳入顯卡,由片元着色器程序算出每一個片元所屬的房間,而後在傳入的數據中找到這個房間的顏色,這個思路相似OpenGL中的3D紋理。

一種將數萬條數據傳入顯卡的合理方式是將數據轉化爲一張圖片,交由顯卡的紋理採樣器進行處理,我把這種用來傳輸數據的紋理命名爲「數據紋理」。剛好我但願傳入的數據正是房間對應的地形顏色,與圖片的數據結構相符。

這裏創建了一個隱形的canvas用來把每一個房間的顏色放入數據紋理的對應像素。

b、處理得到的行星信息,據此獲取每一個地區塊的數據:(這段代碼的執行效率較低)

 1                var str_res=xmlHttp.responseText;//從h2數據庫獲取到的是一個dom文檔,要從中找到所須要的字段
 2                         xmlHttp.abort();
 3                         var div=document.createElement("div");
 4                         //div.innerHTML=str_res;//這樣作會報錯,由於加載整個dom時,標籤中的引用和代碼都會被執行!!!!
 5                         div.innerHTML=str_res.substring(str_res.search(/<table/),str_res.search(/<\/table>/)+8);
 6                         div.style.display=false;
 7                         //document.getElementById("div_allbase").appendChild(div);不須要這一句
 8                         var tr=div.getElementsByTagName("tr")[1];//從返回的數據表裏提取出數據部分
 9                         var tds=tr.getElementsByTagName("td");
10                         min_floor=parseInt(tds[0].innerHTML);
11                         max_floor=parseInt(tds[1].innerHTML);
12                         width_room=parseInt(tds[2].innerHTML);//房間最多的一層有多少房間
13                         height_floor=min_floor+max_floor+1;//總共有多少層
14                         var int_wh=width_room>height_floor?width_room:height_floor;//取寬高中較大的值
15                         var int_size=newland.FindPower(int_wh);//找到大於size的最小的2的整數次冪,注意!!!!
16                         can_temp.width=int_size;
17                         can_temp.height=int_size;
18                         context_temp=can_temp.getContext("2d");
19                         context_temp.fillStyle="rgba(0,0,255,1)";
20                         context_temp.fillRect(0,0,int_size,int_size);
21                         imagedata_temp=context_temp.getImageData(0,0,int_size,int_size);//取canvas的像素數據
22 
23                         arr_floorroom=[];
24                         pbeta=0;//認爲每一層的弧度區分度是必定的
25                         arr_palpha=[];//每一層內的房間的弧度區分度不一樣
26                         var arr_temp=JSON.parse(localStorage.getItem("arr_floorroom"));//讀取本地存儲
27 
28                         if(arr_temp&&arr_temp.length>0)//若是有本地持久化存儲就使用本地的存儲,節省時間
29                         {//經測試Chrome瀏覽器的5M空間正好能夠放下一個行星的數據
30                             strsrc_dqk=JSON.parse(localStorage.getItem("strsrc_dqk"));
31                             arr_floorroom=arr_temp;
32                             arr_palpha=JSON.parse(localStorage.getItem("arr_palpha"));
33                             pbeta=JSON.parse(localStorage.getItem("pbeta"));
34 
35                         }
36                         else
37                         {
38                             for(var i=min_floor;i<=max_floor;i++)//讀取每一層的數據
39                             {
40                                 Url="http://127.0.0.1:8082/query.do?jsessionid="+jsessionid;
41                                 Argv="sql=select id,beta,pbeta,alpha,palpha,weight,floor,room from tab_dqk" +
42                                         " where planetid='test' and floor="+i;//查找這個行星的地區塊層數
43                                 //使用同步Ajax請求保證進度同步
44                                 Request(xmlHttp,"POST",Url,false,Argv,"application/x-www-form-urlencoded",QueryCallBack,0);
45                             }
46                         }
47                         localStorage.setItem("arr_floorroom",JSON.stringify(arr_floorroom));
48                         localStorage.setItem("arr_palpha",JSON.stringify(arr_palpha));
49                         localStorage.setItem("pbeta",JSON.stringify(pbeta));
50                         //大小超過了本地存儲限制
51                         xmlHttp.abort();
52                         DrawPlanet();//繪製行星

其中FindPower是我編寫的一個尋找2的整數次冪的方法:

 1 //根據正方形邊長找大於size的最小的2的整數次冪
 2 newland.FindPower=function(size)
 3 {
 4     var int1=Math.ceil(Math.log2(size));
 5     return Math.pow(2,int1);
 6 }
 7 //根據數據長度找最小的2的整數次冪
 8 newland.FindPower2=function(len)
 9 {
10     var int1=Math.sqrt(len);
11     return this.FindPower(int1);
12 }

藉助這一方法,咱們把datatexture(數據紋理)設置爲邊長是2的整數次冪的正方形圖片。在原生WebGL中,紋理圖片的邊長必須是2的整數次冪,不然會發生錯誤,而Babylon.js雖然支持使用任意尺寸的圖片,但非標準圖片在傳入顯卡時會自動進行線性採樣模式的拉伸,而致使圖片顏色交界處的模糊,這種模糊對於數據紋理準確性的影響是致命的。

您能夠本身搜索一下「最近點採樣」與「線性紋理採樣」的知識,datatexture必須使用最近點採樣。

palpha是水平區分度,表示每一個房間的中心到房間牆壁的弧度,顯然越靠近兩極房間越少,水平區分的值也就越高,arr_palpha保存了每一層的水平區分度,是一個長度爲199的數組。

c、處理每一層的地區塊數據:

 1                var str_res=xmlHttp.responseText;//這時已經在h2服務端創建登陸狀態
 2                         //xmlHttp.abort();
 3                         var div=document.createElement("div");
 4                         div.innerHTML=str_res.substring(str_res.search(/<table/),str_res.search(/<\/table>/)+8);
 5                         var trs=div.getElementsByTagName("tr");
 6                         var len=trs.length;
 7                         var int_floor=parseInt(trs[1].getElementsByTagName("td")[6].innerHTML);
 8                         if(!arr_floorroom[int_floor])//這裏認爲只有一個行星,在實際使用時,數組還要加上一維
 9                         {
10                             arr_floorroom[int_floor]=[];
11                         }
12                         if(pbeta==0)
13                         {
14                             pbeta=parseFloat(trs[1].getElementsByTagName("td")[2].innerHTML);
15                         }
16                         console.log(int_floor+"/"+max_floor+">"+(len-1));//輸出一下處理進度
17                         arr_palpha.push(parseFloat(trs[1].getElementsByTagName("td")[4].innerHTML));
18                         for(var i=1;i<len;i++)//對於這一層中的每一個room
19                         {
20                                 var tds=trs[i].getElementsByTagName("td");
21                                 var int_room=parseInt(tds[7].innerHTML);
22                                 var arr_landtype={land_textblack:[0,0,0,255],land_textyellow:[255,255,0,255]}
23                                 //保存在內存中的數據結構
24                                 //var obj=eval(tds[5].innerHTML);
25                                 var obj={};
26                                 eval("obj="+tds[5].innerHTML);
27                                 arr_floorroom[int_floor][int_room]={id:tds[0],beta:tds[1].innerHTML,pbeta:tds[2].innerHTML,alpha:tds[3].innerHTML
28                                     ,palpha:tds[4].innerHTML,weight:obj};
29                                 //使用何種數據結構傳遞到顯卡?使用一個超長數組arr_set4?《-這是不行的
30                                 var num1= 0,num2= 0,num3= 0,num4= 255;
31                                 for(key in obj)//將每一種顏色的三個通道加權到一塊兒
32                                 {
33                                     var num_key=obj[key];
34                                     num1+=(num_key*arr_landtype[key][0]);
35                                     num2+=(num_key*arr_landtype[key][1]);
36                                     num3+=(num_key*arr_landtype[key][2]);
37                                 }
38                                 var index= (int_floor+max_floor)*4*can_temp.width+(i-1)*4;//每一個room由4個元素組成
39                                 imagedata_temp.data[index]=num1;//這裏存的真的是顏色
40                                 imagedata_temp.data[index+1]=num2;
41                                 imagedata_temp.data[index+2]=num3;
42                                 imagedata_temp.data[index+3]=num4;
43                         }

由於100km*100km的地區塊在近距離視角下也是一個很大的區域,下一步計劃在拉近視角時,在一個地區塊中再生成更多種類的地形,因此所謂「地區塊的地形」,實際上是其內包含的各類地形的比例分配,在遠處看時地區塊的顏色是其包含的各類地形的加權。

這段測試裏我使用了兩種地形:純黑色的land_textblack和黃色的land_textyellow,而兩種地區塊的地形則分別是百分之百的land_textblack和land_textyellow。顏色的加權值被放進了canvas的像素中。

d、繪製行星:

 1 //用glsl和Babylon.js結合的方式繪製行星
 2     function DrawPlanet()
 3     {
 4         var amigaMaterial = new BABYLON.ShaderMaterial("amiga2", scene,{
 5                     vertexElement: "sh2v4.sh",
 6                     fragmentElement: "sh2f4.sh",
 7                 },
 8                 {
 9                     attributes: ["position"],
10                     uniforms: ["worldViewProjection","worldView"]
11                 });
12         amigaMaterial.doNotSerialize=true;
13         sphere1.material=amigaMaterial;
14         if(strsrc_dqk=="")
15         {
16             context_temp.putImageData(imagedata_temp,0,0);
17             strsrc_dqk=can_temp.toDataURL("image/png");//將canvas轉化爲dataurl
18             localStorage.setItem("strsrc_dqk",JSON.stringify(strsrc_dqk));
19         }
20         var utexturedqk = new BABYLON.Texture.CreateFromBase64String(strsrc_dqk,"utexturedqk", scene
21                 ,false,false,BABYLON.Texture.NEAREST_NEAREST);//將dataurl轉化爲Babylon.js紋理
22         amigaMaterial.setTexture("utexturedqk",utexturedqk);//將紋理和顯卡採樣器關聯
23         amigaMaterial.setFloat("wid_utexturedqk",can_temp.width);//數據紋理寬度,將內存中的變量和顯卡中的通用變量關聯
24         amigaMaterial.setFloat("hei_utexturedqk",can_temp.width);
25         amigaMaterial.setFloat("pbeta",pbeta);//層間區分角度
26 
27         var size=newland.FindPower2(arr_palpha.length);//注意!!!!
28         var strsrc_palpha=newland.TranArrToPng1(arr_palpha,size,size);//每一層內的房間區分角度,用4個元素保存一個浮點數
29         var utexturepalpha = new BABYLON.Texture.CreateFromBase64String(strsrc_palpha,"utexturepalpha", scene
30                 ,true,false,BABYLON.Texture.NEAREST_NEAREST);
31         amigaMaterial.setTexture("utexturepalpha",utexturepalpha);
32         amigaMaterial.setFloat("wid_utexturepalpha",size);//room區分度的紋理寬度
33         amigaMaterial.setFloat("hei_utexturepalpha",size);
34 
35         amigaMaterial.setFloat("uarrpalphalen",arr_palpha.length);
36         amigaMaterial.setFloat("max_floorf",max_floor);//Babylon.js不支持傳遞整形量??GpenGL中int也是以float形式計算的!!!!
37         amigaMaterial.setFloat("MathPI",Math.PI);
38 
39         amigaMaterial.onCompiled=function()//Babylon.js文檔中寫effect是material的一個內容,而material須要一個「編譯過程」,編譯以後的material才具有effect屬性
40         {//並且對Babylon.js來講,material能傳遞的變量類型比較少,好比不能傳遞整形量,而effect則能夠傳遞更多的數據類型
41          //amigaMaterial.getEffect().setArray("uarrpalpha",arr);//每一層水平區分度*/effect能夠向顯卡傳遞數組
           //console.log(amigaMaterial.getEffect());        
      }
42     }

這裏咱們要考慮使用何種方式把長度爲199的浮點型數組arr_palpha傳入顯卡,事實上OpenGL支持傳入數組型通用變量,但對數組的支持分紅兩種:一種是咱們前面看到的vec三、vec4這類向量數組,glsl把整個向量看作一個變量,而另外一種相似「uniform float uarrpalpha[500]」的自定義數組則是把數組中的每個元素都看作一個uniform變量處理!(?)。

根據StackOverFlow上一個外國同行的試驗,OpenGL最多隻能支持200個左右的uniform變量(?),這意味着咱們難以直接用數組的方式傳入arr_palpha。另外在WebGL1.0中的glsl不支持不定長度的數組,這意味着咱們必須在編寫着色器代碼前對數組的大小有恰當的估計,或者根據行星的大小臨時調節着色器代碼。

在glsl中使用長數組的另外一個問題是:glsl居然不支持直接用臨時賦值的變量做爲數組索引!相似

1 int i=1;
2 float f=arr[i+1];

這種數組用法是不容許的!!!!

要將計算結果做爲數組的索引只能使用if else或者switch case枚舉出每一種對應的狀況,或者使用:

 1 float getData500(float data[500],int id) {
 2     int len=int(floor(uarrpalphalen+0.5));
 3     for (int i=0; i<500; i++) {
 4         if(i>=len)//i不能和很是量比較!!只好換個方式限制它
 5         {
 6             return 0.0;
 7         }
 8         if (i == id) return data[i];
 9     }
10 }

其中id是計算出來的索引,另外glsl的for循環的中段也不支持「i<len」這種寫法,i必須小於一個明確的常數。

所以改成使用datatexture傳遞水平區分度數組

然而使用datatexture時又遇到一個問題,canvas會自動把顏色份量轉化爲0到255的整數,而這裏的水平區分度全是0到1之間的小數,會被自動轉爲0或1,爲解決這一問題把水平區分度數據轉化爲科學計數法並用像素表示:

 1 //將一個浮點數組轉化爲DataTexture,這是浮點數小於1的狀況,要注意canvas和webgl對顏色屬性的自動處理!!!!
 2 newland.TranArrToPng1=function(arr,width,height)
 3 {
 4     var can_temp=document.createElement("canvas");
 5     can_temp.width=width;
 6     can_temp.height=height;
 7     var context=can_temp.getContext("2d");
 8     context.fillStyle="rgba(0,0,255,1)";//多餘的位都是1?
 9     context.fillRect(0,0,width,height);
10     var imagedata=context.getImageData(0,0,width,height);
11     var len=arr.length;//小數部分會自動四捨五入!!!!默認palpha一定小於1
12     for(var i=0;i<len;i+=1)
13     {
14         var str_num=arr[i]+"";
15         //var int_0=str_num.indexOf();
16         var len_str=str_num.length;
17         var count_0=0;
18         for(var j=0;j<len_str;j++)
19         {
20             if(str_num[j]=="0"||str_num[j]==".")
21             {
22                 continue;
23             }
24             else
25             {
26                 count_0=j;//找到第一個非零數
27                 break;
28             }
29         }
30         var num1=parseInt(str_num.substr(count_0,2));
31         var num2=parseInt(str_num.substr(count_0+2,2));
32         //var num3=parseInt(str_num.substr(count_0+4,2));
33         var num4=4+(count_0-2);
34         imagedata.data[i*4]=num1;//科學計數法:用像素顏色的第一第二個份量保存四位有效數字,用第四個份量保存10的負指數
35         imagedata.data[i*4+1]=num2;
36         imagedata.data[i*4+2]=num4;
37         //imagedata.data[i*4+3]=num4;
38     }
39     context.putImageData(imagedata,0,0);
40     var strsrc_palpha=can_temp.toDataURL("image/png");
41     //can_temp.dispose();
42     can_temp=null;
43     return strsrc_palpha;
44 }

執行程序,看到運行效果與設計有所誤差:

南半球的黑色地區塊少了不少行,沒有造成預計的棋盤形,時間有限沒有詳細調試

拉近相機:

能夠看到每一個地區塊的邊界清晰可見,沒有發生模糊。

使用console.log輸出用到的兩個dataurl做爲參考:

四、着色器代碼:

a、頂點着色器:

 1 uniform mat4 worldViewProjection;
 2 uniform mat4 worldView;
 3 attribute vec3 position;
 4 
 5 varying vec3 vPosition;
 6 varying vec3 oPosition;//自身座標系裏的位置
 7 
 8 void main(){
 9     gl_Position=worldViewProjection*vec4(position,1);
10     vPosition=vec3(worldView*vec4(position,1));
11     oPosition=position;
12 }

咱們前面生成地形的計算都是在行星的自身座標系裏進行的,因此在片元着色器裏也須要以行星的自身座標系爲參考肯定地區塊的層數和房間號,因此把一個不通過任何矩陣變換的頂點位置信息傳入片元着色器。

b、片元着色器:

 1 precision highp float;
 2 //varying vec4 vColor;
 3 varying vec3 vPosition;
 4 varying vec3 oPosition;
 5 //uniform vec4 uColor;
 6 uniform sampler2D utexturedqk;//地區塊數據紋理的採樣器
 7 uniform float wid_utexturedqk;//數據紋理的寬高
 8 uniform float hei_utexturedqk;
 9 
10 uniform sampler2D utexturepalpha;//一個單元裏保存了四個元素!!!!
11 //uniform vec3 uarrdqk[60000];//es3.0以前的glsles不支持隱含數組!!!!
12 uniform float pbeta;
13 uniform float wid_utexturepalpha;
14 uniform float hei_utexturepalpha;
15 //uniform float uarrpalpha[500];//用來測試的行星只有199層,預設爲500層應該夠了
16 uniform float uarrpalphalen;
17 uniform float max_floorf;
18 uniform float MathPI;
19 
20 float getDataVec4(vec4 data,int id) {
21     for (int i=0; i<4; i++) {
22         if (i == id) return data[i];
23     }
24 }
25 float getData500(float data[500],int id) {
26     int len=int(floor(uarrpalphalen+0.5));
27     for (int i=0; i<500; i++) {
28         if(i>=len)//i不能和很是量比較!!只好換個方式限制它
29         {
30             return 0.0;
31         }
32         if (i == id) return data[i];
33     }
34 }
35 
36 void main()
37 {
38     //vec4 tempColor=uColor;//es3.0以前不支持round!!!!
39     //glsl事實上以float爲計算基準
40     //int max_floor=int(floor(max_floorf+0.5));
41     //算層數
42     float r=sqrt(oPosition.x*oPosition.x+oPosition.y*oPosition.y+oPosition.z*oPosition.z);//這個片元到球心的距離
43     float beta=asin(oPosition.y/r);//俯仰角
44     //int int_beta=int(floor((beta/(pbeta*2.0))+0.5));
45     float count_beta=(beta/(pbeta*2.0));
46     //int index_beta=int(floor(count_beta+ max_floorf+0.5));
47     float index_beta=floor(count_beta+ max_floorf+0.5);//第幾層
48     //int roomcount=0;
49     //使用數據紋理法,取這一層的區分度
50     //int int1=int(floor(mod(index_beta,4.0)+0.5));51     //float float1=(index_beta/4.0);
52     float floatu=(mod(index_beta,wid_utexturepalpha))/(wid_utexturepalpha)+(0.5/wid_utexturepalpha);//u是x軸座標,v是y軸座標
53     float floatv=(((index_beta)/(wid_utexturepalpha)))/(hei_utexturepalpha)+(0.5/(wid_utexturepalpha*hei_utexturepalpha));
54     vec2 UVpalpha=vec2(floatu,floatv);//上面計算的uv座標加了一個偏移量,防止座標正好落在兩個像素的邊界上
55     vec4 vec4palphas=texture2D(utexturepalpha, UVpalpha);//glsl中的顏色爲0到1.0,因此要乘以255.0得到傳入的科學計數法
56     float palpha=(vec4palphas[0]*255.0*100.0+vec4palphas[1]*255.0)/pow(10.0,vec4palphas[2]*255.0);
57     //float palpha=getData500(uarrpalpha,int(floor(index_beta+0.5)));//改成嘗試數組法傳遞數據
58     //取這一層的轉角
59     float alpha=atan(oPosition.z,oPosition.x);//標準反正切函數有兩個參數!!
60     if(alpha<0.0)
61     {
62         alpha+=(MathPI*2.0);
63     }
64     //取地區塊數據紋理的座標
65     float floatu2=(alpha/(palpha*2.0))/wid_utexturedqk;
66     float floatv2=index_beta/hei_utexturedqk+0.5/hei_utexturedqk;
67     vec2 UVdqk=vec2(floatu2,floatv2);
68     gl_FragColor=texture2D(utexturedqk, UVdqk);
69     //gl_FragColor=vec4palphas;
70     //gl_FragColor=texelFetch(utexturedqk,ivec2(int(floor(alpha/(palpha*2.0)+0.5)),int(floor(index_beta+0.5))),0);//這個整數像素的方法是WebGL2開始加入的!!!!
71     //gl_FragColor=vec4(1.0*floatu,1.0*floatv,1.0*floatv2,1.0);//紅,綠,藍結果不是0就是1??!!
72 
73     //int index_dqk=roomcount-1+int(floor((alpha/palpha)+0.5));
74     //vec4 tempColor=vec4(uarrdqk[index_dqk],1.0);
75 
76     //float float_3=index_beta/(max_floorf*2.0);
77     //float float_4=oPosition.y/5.0;
78     //canvas的imagedata用255,255,255,255定義顏色通道,而glsl用1.0,1.0,1.0,1.0定義!!!!
79 
80 
81 }

glsl語言不支持字符串類型,WebGL1.0也不支持從顯卡反寫數據到內存,一種可行的調試方法是將某個計算結果轉化爲顏色顯示在屏幕上,而後用拾色器提取值。

4、根據規則生成隨機的行星表面地形(testarenas.html)

一、生成地區塊的基本數據結構:

 1 function CookDqk()//生成地區塊,每一floor的每一個room
 2     {
 3         var size_dqk=100;//每一個地區塊的長寬都是100km
 4         var r_planet=perimeter/(2*Math.PI);//行星的半徑
 5         var len_beta=sswr(((perimeter/2)/size_dqk)/2);//經過弧度來分層!!100
 6         pbeta=(Math.PI/4)/len_beta;
 7         //對於每一層,
 8         for(var i=-len_beta;i<=len_beta;i++)
 9         {
10 
11             var rad_beta=(Math.PI/2)*(i/len_beta);
12             var r_floor=Math.cos(rad_beta)*r_planet;//這一層的半徑
13             var len_alpha_floor=sswr((r_floor*2*Math.PI)/size_dqk);
14             var palpha=Math.PI/len_alpha_floor;//每個地區塊的角度邊界,在這個邊界範圍內即屬於這個地區塊
15             arr_palpha.push(palpha);
16             var beta=i*pbeta*2;
17             //console.log(i+"/"+len_beta+">"+len_alpha_floor);
18             var arr1=[];
19             //對於圓環上的每個片
20             for(var j=0;j<len_alpha_floor;j++)
21             {
22                 var obj={};
23                 obj.palpha=palpha;
24                 obj.alpha=j*palpha*2;
25                 obj.pbeta=pbeta;
26                 obj.beta=beta;
27                 //obj.weight={};
28                 obj.floor=i;
29                 obj.room=j;
30                 obj.countcook=0;
31                 obj.altitude=0;
32                
33                 arr1.push(obj);
34               
35             }
36             if(arr1.length>0)
37             {
38                 arr_floorroom.push(arr1);
39             }
40 
41         }
42         CookDqk2();//對生成的數據結構進行  規律隨機填充
43     }
View Code

二、使用正態隨機數與加和平均肯定每一個地區塊的海拔

  1 //使用正態隨機數和加和平均肯定每一個地區塊的海拔
  2     function CookDqk2()
  3     {
  4         var len=arr_floorroom.length;
  5         //生成初始的隨機正態隨機海拔
  6         console.log("生成初始的隨機正態隨機海拔");
  7         for(var i=0;i<len;i++)
  8         {
  9             //console.log(i+" in "+len);
 10             var len2=arr_floorroom[i].length;
 11             for(var j=0;j<len2;j++)
 12             {
 13                 var obj=arr_floorroom[i][j];
 14                 obj.altitude=dc1.getNumberInNormalDistribution(-10,1000);//平均海拔是-10,常見的海拔在正負1000之內
 15                 if(obj.altitude<-10000)
 16                 {
 17                     obj.altitude=-10000;
 18                 }
 19                 else if(obj.altitude>10000)
 20                 {
 21                     obj.altitude=10000;
 22                 }
 23                 obj.countcook=1;
 24                 if(i%2==1)//若是是奇數層,room偏移一個識別範圍,這樣地形看起來更天然
 25                 {
 26                     obj.alpha+=obj.palpha;
 27                 }
 28             }
 29         }
 30         //使用加和平均方法使海拔趨於連續(高度平滑)
 31         console.log("使用加和平均方法使海拔趨於連續");
 32         for(var i=0;i<len;i++)//將地區塊的海拔和周圍相鄰的全部地區塊的海拔相加取平均值,做爲這個地區塊的海拔
 33         {
 34             console.log(i+" in "+len);
 35             var len2=arr_floorroom[i].length;
 36             for(var j=0;j<len2;j++)
 37             {
 38                 var obj=arr_floorroom[i][j];
 39                 obj.altitude1=obj.altitude;
 40                 if(i>0)//考慮這個room下面的floor
 41                 {
 42                     //var alpha=obj.alpha;
 43                     var len3=arr_floorroom[i-1].length;
 44                     for(var k=0;k<len3;k++)//遍歷下層的room
 45                     {
 46                         var subplpha=Math.abs(arr_floorroom[i-1][k].alpha-obj.alpha);
 47                         if(subplpha>Math.PI)
 48                         {
 49                             subplpha=Math.PI*2-subplpha;
 50                         }
 51                         if(subplpha<=(obj.palpha+arr_floorroom[i-1][k].palpha))
 52                         {//對這個地區塊有影響
 53                             obj.altitude1+=arr_floorroom[i-1][k].altitude;
 54                             obj.countcook++;
 55                         }
 56                     }
 57 
 58                 }
 59                 if(i<len-1)//考慮這個room上面的floor
 60                 {
 61                     var len3=arr_floorroom[i+1].length;
 62                     for(var k=0;k<len3;k++)//遍歷上層的room
 63                     {
 64                         var subplpha=Math.abs(arr_floorroom[i+1][k].alpha-obj.alpha);
 65                         if(subplpha>Math.PI)
 66                         {
 67                             subplpha=Math.PI*2-subplpha;
 68                         }
 69                         if(subplpha<=(obj.palpha+arr_floorroom[i+1][k].palpha))
 70                         {//對這個地區塊有影響
 71                             obj.altitude1+=arr_floorroom[i+1][k].altitude;
 72                             obj.countcook++;
 73                         }
 74                     }
 75                 }
 76                 //考慮本層的相鄰元素
 77                 if(j==0)
 78                 {
 79                     obj.altitude1+=arr_floorroom[i][1].altitude;
 80                     obj.altitude1+=arr_floorroom[i][len2-1].altitude;
 81                     obj.countcook+=2;
 82                 }else if(j==(len2-1))
 83                 {
 84                     obj.altitude1+=arr_floorroom[i][0].altitude;
 85                     obj.altitude1+=arr_floorroom[i][len2-2].altitude;
 86                     obj.countcook+=2;
 87                 }
 88                 else{
 89                     obj.altitude1+=arr_floorroom[i][j-1].altitude;
 90                     obj.altitude1+=arr_floorroom[i][j+1].altitude;
 91                     obj.countcook+=2;
 92                 }
 93             }
 94         }
 95         var min_altitude= 0,max_altitude=0;
 96         console.log("去除總權值");
 97         for(var i=0;i<len;i++)
 98         {
 99             console.log(i+" in "+len);
100             var len2=arr_floorroom[i].length;
101             for(var j=0;j<len2;j++)
102             {
103                 var obj=arr_floorroom[i][j];
104                 obj.altitude=obj.altitude1/obj.countcook;
105                 if(obj.altitude<min_altitude)
106                 {
107                     min_altitude=obj.altitude;
108                 }
109                 if(obj.altitude>max_altitude)
110                 {
111                     max_altitude=obj.altitude;
112                 }
113                 //delete obj.altitude1;
114             }
115         }
116         console.log("最低、最高海拔爲:"+min_altitude+"、"+max_altitude);
117         //根據海拔高度與機率規則肯定海洋與陸地,根據緯度和高度肯定陸地的類型(高度達到必定程度後優於緯度)
118         CookDqk3();
119     }

 關於正態隨機數的知識能夠參考這篇文章:https://www.cnblogs.com/zztt/p/4025207.html

三、根據海拔高度和緯度肯定地形:

  1 function CookDqk3()
  2     {
  3         console.log("開始生成地區塊級地形");
  4         var len=arr_floorroom.length;
  5         for(var i=0;i<len;i++) {
  6             console.log(i+" in "+len);
  7             var len2 = arr_floorroom[i].length;
  8             for (var j = 0; j < len2; j++)
  9             {
 10                 var obj=arr_floorroom[i][j];
 11                 getLandtypeDqk(obj);//根據規則肯定這個地區塊的地形
 12             }
 13         }
 14         //地區塊平滑
 15         console.log("地區塊平滑");
 16         for(var i=0;i<len;i++)
 17         {
 18             console.log(i+" in "+len);
 19             var len2=arr_floorroom[i].length;
 20             for(var j=0;j<len2;j++)
 21             {
 22                 var obj=arr_floorroom[i][j];
 23 
 24                 if(i>0)//考慮這個room下面的floor
 25                 {
 26                     //var alpha=obj.alpha;
 27                     var len3=arr_floorroom[i-1].length;
 28                     for(var k=0;k<len3;k++)//遍歷下層的room
 29                     {
 30                         var obj1=arr_floorroom[i-1][k];
 31                         var subplpha=Math.abs(obj1.alpha-obj.alpha);
 32                         if(subplpha>Math.PI)
 33                         {
 34                             subplpha=Math.PI*2-subplpha;
 35                         }
 36                         if(subplpha<=(obj.palpha+obj1.palpha))
 37                         {//對這個地區塊有影響
 38                             if(!obj.landtypedqk[obj1.type2])
 39                             {
 40                                 obj.landtypedqk[obj1.type2]=obj1.effect;//這一種地形的權重
 41                             }
 42                             else
 43                             {
 44                                 obj.landtypedqk[obj1.type2]+=obj1.effect;
 45                             }
 46                             obj.landtypedqkeffect+=obj1.effect;//全部地形的權重
 47                         }
 48                     }
 49 
 50                 }
 51                 if(i<len-1)//考慮這個room上面的floor
 52                 {
 53                     var len3=arr_floorroom[i+1].length;
 54                     for(var k=0;k<len3;k++)//遍歷上層的room
 55                     {
 56                         var obj1=arr_floorroom[i+1][k];
 57                         var subplpha=Math.abs(obj1.alpha-obj.alpha);
 58                         if(subplpha>Math.PI)
 59                         {
 60                             subplpha=Math.PI*2-subplpha;
 61                         }
 62                         if(subplpha<=(obj.palpha+obj1.palpha))
 63                         {//對這個地區塊有影響
 64                             if(!obj.landtypedqk[obj1.type2])
 65                             {
 66                                 obj.landtypedqk[obj1.type2]=obj1.effect;
 67                             }
 68                             else
 69                             {
 70                                 obj.landtypedqk[obj1.type2]+=obj1.effect;
 71                             }
 72                             obj.landtypedqkeffect+=obj1.effect;
 73                         }
 74                     }
 75                 }
 76                 //考慮本層的相鄰元素
 77                 if(j==0)
 78                 {
 79                     var obj1=arr_floorroom[i][1];
 80                     if(!obj.landtypedqk[obj1.type2])
 81                     {
 82                         obj.landtypedqk[obj1.type2]=obj1.effect;
 83                     }
 84                     else
 85                     {
 86                         obj.landtypedqk[obj1.type2]+=obj1.effect;
 87                     }
 88                     obj.landtypedqkeffect+=obj1.effect;
 89                     var obj1=arr_floorroom[i][len2-1];
 90                     if(!obj.landtypedqk[obj1.type2])
 91                     {
 92                         obj.landtypedqk[obj1.type2]=obj1.effect;
 93                     }
 94                     else
 95                     {
 96                         obj.landtypedqk[obj1.type2]+=obj1.effect;
 97                     }
 98                     obj.landtypedqkeffect+=obj1.effect;
 99                 }
100                 else if(j==(len2-1))
101                 {
102                     var obj1=arr_floorroom[i][0];
103                     if(!obj.landtypedqk[obj1.type2])
104                     {
105                         obj.landtypedqk[obj1.type2]=obj1.effect;
106                     }
107                     else
108                     {
109                         obj.landtypedqk[obj1.type2]+=obj1.effect;
110                     }
111                     obj.landtypedqkeffect+=obj1.effect;
112                     var obj1=arr_floorroom[i][len2-2];
113                     if(!obj.landtypedqk[obj1.type2])
114                     {
115                         obj.landtypedqk[obj1.type2]=obj1.effect;
116                     }
117                     else
118                     {
119                         obj.landtypedqk[obj1.type2]+=obj1.effect;
120                     }
121                     obj.landtypedqkeffect+=obj1.effect;
122                 }
123                 else{
124                     var obj1=arr_floorroom[i][j-1];
125                     if(!obj.landtypedqk[obj1.type2])
126                     {
127                         obj.landtypedqk[obj1.type2]=obj1.effect;
128                     }
129                     else
130                     {
131                         obj.landtypedqk[obj1.type2]+=obj1.effect;
132                     }
133                     obj.landtypedqkeffect+=obj1.effect;
134                     var obj1=arr_floorroom[i][j+1];
135                     if(!obj.landtypedqk[obj1.type2])
136                     {
137                         obj.landtypedqk[obj1.type2]=obj1.effect;
138                     }
139                     else
140                     {
141                         obj.landtypedqk[obj1.type2]+=obj1.effect;
142                     }
143                     obj.landtypedqkeffect+=obj1.effect;
144                 }
145             }
146         }
147         console.log("對每一個地區塊進行加權併入庫");
148         for(var i=0;i<len;i++)//對每一個地區塊進行加權,這段代碼執行很慢
149         {
150             var len2=arr_floorroom[i].length;
151             console.log(i+" in "+len);
152             for(var j=0;j<len2;j++)
153             {
154 
155                 var obj=arr_floorroom[i][j];
156                 obj.altitude=obj.altitude/obj.countcook;
157                 var rate_type1final=Math.random()*obj.landtypedqkeffect;
158                 var rate_type1final_count=0;
159                 obj.type2final="默認dqk";
160                 for(key in obj.landtypedqk)
161                 {
162                     rate_type1final_count+=obj.landtypedqk[key];//這一種地形的權重
163                     if(rate_type1final<rate_type1final_count)//若是隨機數小於這種地形的累積權重
164                     {
165                         obj.type2final=key;
166                         break;
167                     }
168                 }
169                 //在這裏把這個地區塊插入數據庫?不知道什麼緣由POST方法失敗了,改用GET方法
170                 /*Url="http://127.0.0.1:8082/query.do?jsessionid="+jsessionid;
171                 Argv="sql=insert into tab_dqk values(uuid(),'test1',"+obj.beta+","+obj.pbeta+","+obj.alpha+","+obj.palpha+",'"
172                         +obj.type2final+"',"+obj.floor+","+obj.room+","+obj.altitude+")";
173                 //使用同步Ajax請求保證進度同步,在連續使用同步Ajax時不須要xmlHttp.abort()!!!!
174                 Request(xmlHttp,"POST",Url,false,Argv,"application/x-www-form-urlencoded",PushChessCallBack,0);*/
175                 Url="http://127.0.0.1:8082/query.do?jsessionid="+jsessionid+"&sql=insert into tab_dqk values(uuid(),'test1',"+obj.beta+","+obj.pbeta+","+obj.alpha+","+obj.palpha+",'"
176                         +obj.type2final+"',"+obj.floor+","+obj.room+","+obj.altitude+")"
177                 Argv="";
178                 Request(xmlHttp,"GET",Url,false,Argv,"application/x-www-form-urlencoded",PushChessCallBack,0);
179             }//這裏改變了一下數據結構,直接用weight字段存儲肯定了的地區塊級地形
180         }
181         CookDqk4();//着色
182     }

getLandtypeDqk是根據海拔和緯度肯定地形的方法:(這個方法效率很低)

 1 function getLandtypeDqk(obj)
 2     {
 3         var height=obj.altitude;//對於這個地區塊
 4         var beta=obj.beta;
 5         var rate_land=Math.random();
 6         for(key in tab_landtypedqk)//這個方法並不能保證順序!!!!//for key和eval佔用了大量時間????
 7         {//按順序查找每個地區塊級地形是否符合條件,
 8             if(eval(tab_landtypedqk[key].eval))//JavaScript語言的一大特色是能夠隨時把字符串轉化爲可執行代碼,
 9             {//這使得JavaScript語言能夠很是靈活,可是會下降執行效率和安全性
10                 obj.type1=key;
11                 var count_rate=0;//用來累加機率
12                 var obj1=tab_landtypedqk[key];
13                 for(key2 in obj1)
14                 {
15                     if(key2!="eval")
16                     {
17                         var rate_type2=Math.random();
18                         count_rate+=obj1[key2].rate;
19                         if(rate_type2<count_rate)
20                         {
21                             obj.type2=key2;
22                             obj.effect=obj1[key2].effect;//對周邊地塊的影響程度
23                             break;
24                         }
25                     }
26                 }
27                 break;
28             }
29         }
30         if (!obj.type1)//若是這個地區塊沒有被分配地形
31         {
32             obj.type1="未定義";
33             obj.type2="默認dqk";
34             obj.effect=0;
35         }
36         obj.landtypedqk={};//這三個變量用於對地形進行平滑處理
37         obj.landtypedqk[obj.type2]=obj.effect;
38         obj.landtypedqkeffect=obj.effect;
39     }

在同一海拔和緯度可能有多種地形存在,每一種地形都有一個出現機率,取一個隨機數,若是這個隨機數小於遍歷到這個地形時的機率累積,則將這個地區塊設爲這種地形。

每一種地形還有一個effect屬性,表示這種地形對周邊地形的影響能力,好比若是一片熱帶雨林周圍全被沙漠包圍,那麼這片雨林有很大可能變成沙漠。

海拔和緯度與地形的對應關係設定以下:(tab_datalib.js)

 1 //地區塊地形元素分佈表
 2 var beta_2326=((23+26/60)/180)*Math.PI;//南北迴歸線弧度
 3 var beta_6034=((60+34/60)/180)*Math.PI;//南北極圈弧度
 4 var beta_8=((8)/180)*Math.PI;//赤道附近弧度
 5 var tab_landtypedqk={//rate按從小到大排列生成的隨機數小於哪一個就定爲什麼種地形,effect在卷積平滑階段起做用,表示這個地形對周圍環境的影響程度
 6     "熱帶海洋":{eval:"height<0&&rate_land<0.9&&Math.abs(beta)<beta_2326","熱帶海洋dqk":{rate:1,effect:1}},//eval是判斷地區塊大類型的判斷條件,之後在設計技能效果時也可能要借鑑這裏
 7     "溫帶海洋":{eval:"height<0&&rate_land<0.9&&Math.abs(beta)>beta_2326&&Math.abs(beta)<beta_6034","溫帶海洋dqk":{rate:1,effect:1}},
 8     "寒帶海洋":{eval:"height<0&&rate_land<0.9&&Math.abs(beta)>beta_6034","寒帶海洋dqk":{rate:1,effect:1}},
 9     "溫帶1500米如下":{eval:"rate_land>0.1&&Math.abs(beta)>beta_2326&&Math.abs(beta)<beta_6034&&height<1500","草原dqk":{rate:0.4,effect:1},"森林dqk":{rate:0.4,effect:1},"戈壁dqk":{rate:0.2,effect:2}},
10     "溫帶1500米以上":{eval:"rate_land>0.1&&Math.abs(beta)>beta_2326&&Math.abs(beta)<beta_6034&&height>=1500","雪山dqk":{rate:1,effect:1}},
11     "亞熱帶3000米如下":{eval:"rate_land>0.1&&Math.abs(beta)>beta_8&&Math.abs(beta)<beta_2326&&height<3000","熱帶雨林dqk":{rate:0.5,effect:1},"稀樹草原dqk":{rate:0.5,effect:1}},
12     "亞熱帶3000米以上":{eval:"rate_land>0.1&&Math.abs(beta)>beta_8&&Math.abs(beta)<beta_2326&&height>=3000","雪山dqk":{rate:1,effect:1}},
13     "熱帶3000米如下":{eval:"rate_land>0.1&&Math.abs(beta)<beta_8&&height<3000","熱帶雨林dqk":{rate:0.5,effect:1},"沙漠dqk":{rate:0.5,effect:2}},
14     "熱帶3000米以上":{eval:"rate_land>0.1&&Math.abs(beta)<beta_8&&height>=3000","雪山dqk":{rate:1,effect:1}},
15     "寒帶-100米如下":{eval:"rate_land>0.1&&Math.abs(beta)>beta_6034&&height<-100","草原dqk":{rate:0.4,effect:1},"森林dqk":{rate:0.4,effect:1},"戈壁dqk":{rate:0.2,effect:2}},
16     "寒帶-100到200米之內":{eval:"rate_land>0.1&&Math.abs(beta)>beta_6034&&height<200&&height>=-100","寒帶森林dqk":{rate:0.6,effect:1},"冰川dqk":{rate:0.4,effect:1}},
17     "寒帶200米以上":{eval:"rate_land>0.1&&Math.abs(beta)>beta_6034&&height>200","冰川dqk":{rate:1,effect:1}}
18 }
19 var tab_landtypedqk2={//每一種地區塊的遠觀顏色和內部地貌塊(單位塊?)佔比
20     "默認dqk":{color:[250,126,126],content:{"紅白格dmk":{rate:1,effect:0}}},//徹底紅白格,這種表示錯誤和未定義的地貌塊不會影響周圍
21     "熱帶海洋dqk":{color:[15,63,105],content:{"海洋水面dmk":{rate:0.99,effect:1},"雨林dmk":{rate:0.995,effect:0},"沙灘dmk":{rate:1,effect:0}}},
22     "溫帶海洋dqk":{color:[15,63,105],content:{"海洋水面dmk":{rate:0.99,effect:1},"森林dmk":{rate:0.995,effect:0},"沙灘dmk":{rate:1,effect:0}}},
23     "寒帶海洋dqk":{color:[15,63,105],content:{"海洋水面dmk":{rate:0.5,effect:1},"冰面dmk":{rate:1,effect:1}}},
24     "草原dqk":{color:[93, 153, 63],content:{"草地dmk":{rate:0.95,effect:1},"內陸水面dmk":{rate:1,effect:1}}},
25     "森林dqk":{color:[33,68,44],content:{"森林dmk":{rate:0.95,effect:1},"內陸水面dmk":{rate:1,effect:1}}},
26     "戈壁dqk":{color:[127, 102, 79],content:{"戈壁dmk":{rate:1,effect:1}}},
27     "雪山dqk":{color:[220, 221, 220],content:{"雪地dmk":{rate:0.8,effect:1},"岩石dmk":{rate:1,effect:0}}},
28     "熱帶雨林dqk":{color:[33,68,44],content:{"雨林dmk":{rate:0.95,effect:1},"內陸水面dmk":{rate:1,effect:1}}},
29     "稀樹草原dqk":{color:[117, 118, 68],content:{"稀樹草原dmk":{rate:0.95,effect:1},"內陸水面dmk":{rate:1,effect:1}}},
30     "沙漠dqk":{color:[175, 117, 68],content:{"沙地dmk":{rate:0.99,effect:1},"內陸水面dmk":{rate:0.995,effect:0},"綠洲dmk":{rate:1,effect:0}}},
31     "寒帶森林dqk":{color:[],content:{"寒帶森林dmk":{rate:0.85,effect:1},"雪地dmk":{rate:1,effect:1}}},
32     "冰川dqk":{color:[201, 216, 220],content:{"冰面dmk":{rate:0.85,effect:1},"雪地dmk":{rate:1,effect:1}}}
33 }
34 var tab_landtypedmk={//每一種地貌塊的紋理url
35     "紅白格dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/amiga.jpg",color:[250,126,126]},//對單位的影響,紋理Url,紋理的平均顏色
36     "海洋水面dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/sea.png",color:[15,63,105]},
37     "雨林dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/yulin.png",color:[33,68,44]},
38     "沙灘dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/shatan.png",color:[205, 160, 109]},
39     "森林dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/yulin.png",color:[33,68,44]},//沒找到溫帶森林,暫時用雨林代替
40     "冰面dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/ice.png",color:[201, 216, 220]},
41     "草地dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/grass.png",color:[93, 153, 63]},
42     "內陸水面dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/lake.png",color:[93,143,180]},
43     "戈壁dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/gebi.png",color:[127, 102, 79]},
44     "雪地dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/snow.png",color:[220, 221, 220]},
45     "岩石dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/stone.png",color:[82, 81, 74]},
46     "稀樹草原dmk":{eval_effect:"../../ASSETS/IMAGE/Texture_landtypedmk/xishucaoyuan.png",Url:"",color:[117, 118, 68]},
47     "沙地dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/sand.png",color:[175, 117, 68]},
48     "綠洲dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/lvzhou.png",color:[127, 144, 111]},
49     "寒帶森林dmk":{eval_effect:"",Url:"../../ASSETS/IMAGE/Texture_landtypedmk/lvzhou.png",color:[127, 144, 111]}
50 
51 }

這裏設定每個100km*100km的地區塊能夠由更小的「地貌塊」組成,在極近時地貌塊的地形顯示爲實際的紋理圖,在較遠時地貌塊表現爲紋理圖的平均色,在更遠一些時用地區塊代替地貌塊,地區塊的顏色爲地貌塊的加權。

目前這個切換功能還未編寫,由於時間有限實際地形紋理也沒有仔細設置,地區塊的顏色加權也沒作,直接使用了佔比較多的地貌塊顏色。

提取紋理圖平均顏色的代碼以下:(testpix.html)

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>提取一副固定大小圖片的平均顏色color4</title>
 6 </head>
 7 <body>
 8 <div id="div_allbase">
 9     <canvas style="width: 512px;height: 512px" width="512" height="512" id="can_pic">
10 
11     </canvas>
12 </div>
13 </body>
14 <script>
15     var canvas=document.getElementById("can_pic");
16     window.onload=loadImage;
17     function loadImage()
18     {
19         var context=canvas.getContext("2d");
20         var img=document.createElement("img");
21         img.src="../../ASSETS/IMAGE/Texture_landtypedmk/lvzhou.png";
22         img.onload=function()
23         {//在圖片加載完畢後才能夠在canvas裏繪製
24             context.drawImage(img,0,0);
25             var imagedata_temp=context.getImageData(0,0,512,512);//規定地貌塊紋理圖片的寬高是512
26             var data=imagedata_temp.data;
27             var len=data.length;
28             var color4=[0,0,0,0];
29             for(var i=0;i<len;i+=4)
30             {
31                 color4[0]+=data[i];
32                 color4[1]+=data[i+1];
33                 color4[2]+=data[i+2];
34                 color4[3]+=data[i+3];
35             }
36             var int=512*512;
37             color4[0]=Math.round(color4[0]/int);
38             color4[1]=Math.round(color4[1]/int);
39             color4[2]=Math.round(color4[2]/int);
40             color4[3]=Math.round(color4[3]/int);
41             console.log(color4);
42         }
43 
44     }
45 </script>
46 </html>
View Code

四、生成數據紋理

 1 function CookDqk4()
 2     {
 3         var len=arr_floorroom.length;
 4         //開始生成數據紋理
 5         console.log("開始生成數據紋理");
 6         for(var i=0;i<len;i++) //每一行
 7         {
 8             //console.log(i+" in "+len);
 9             var len2 = arr_floorroom[i].length;
10             for (var j = 0; j < len2; j++) {
11                 var obj = arr_floorroom[i][j];
12                 var index= (i)*4*can_temp.width+(j-1)*4;//每一個room由4個元素組成
13                 var color4=[];
14                 if(tab_landtypedqk2[obj.type2final].color)
15                 {//從地形對象中獲取顏色
16                     color4=tab_landtypedqk2[obj.type2final].color;
17                 }
18                 else
19                 {
20                     color4=[250,126,126];//默認紋理遠觀顏色
21                 }
22                 imagedata_temp.data[index]=color4[0];//這裏存的真的是顏色
23                 imagedata_temp.data[index+1]=color4[1];
24                 imagedata_temp.data[index+2]=color4[2];
25                 imagedata_temp.data[index+3]=255;
26 
27             }
28         }
29 
30     }

而後用和前面相似的方式將數據紋理送入顯卡並進行渲染

着色器代碼以下:

頂點着色器:

 1 uniform mat4 worldViewProjection;
 2 uniform mat4 worldView;
 3 attribute vec3 position;
 4 
 5 varying vec3 vPosition;
 6 varying vec3 oPosition;//自身座標系裏的位置
 7 
 8 void main(){
 9     gl_Position=worldViewProjection*vec4(position,1);
10     vPosition=vec3(worldView*vec4(position,1));
11     oPosition=position;
12 }
View Code

片元着色器:

 1 precision highp float;
 2 //varying vec4 vColor;
 3 varying vec3 vPosition;
 4 varying vec3 oPosition;
 5 //uniform vec4 uColor;
 6 uniform sampler2D utexturedqk;
 7 uniform float wid_utexturedqk;
 8 uniform float hei_utexturedqk;
 9 
10 uniform sampler2D utexturepalpha;//一個單元裏保存了四個元素!!!!
11 //uniform vec3 uarrdqk[60000];//es3.0以前的glsles不支持隱含數組!!!!
12 uniform float pbeta;
13 uniform float wid_utexturepalpha;
14 uniform float hei_utexturepalpha;
15 //uniform float uarrpalpha[500];//用來測試的行星只有199層,預設爲500層應該夠了
16 uniform float uarrpalphalen;
17 uniform float max_floorf;
18 uniform float MathPI;
19 
20 float getDataVec4(vec4 data,int id) {
21     for (int i=0; i<4; i++) {
22         if (i == id) return data[i];
23     }
24 }
25 float getData500(float data[500],int id) {
26     int len=int(floor(uarrpalphalen+0.5));
27     for (int i=0; i<500; i++) {
28         if(i>=len)//i不能和很是量比較!!只好換個方式限制它
29         {
30             return 0.0;
31         }
32         if (i == id) return data[i];
33     }
34 }
35 float getOdevity(float a)//判斷浮點型整數的奇偶性,偶返回0,奇返回1
36 {
37     float b=mod(a,2.0);
38     float c=0.0;
39     if(b>=0.5&&b<1.5)
40     {
41         c=1.0;
42     }
43     return c;
44 }
45 
46 void main()
47 {
48     //vec4 tempColor=uColor;//es3.0以前不支持round!!!!
49     //glsl事實上以float爲計算基準
50     //int max_floor=int(floor(max_floorf+0.5));
51     //算層數
52     float r=sqrt(oPosition.x*oPosition.x+oPosition.y*oPosition.y+oPosition.z*oPosition.z);
53     float beta=asin(oPosition.y/r);//俯仰角
54     //int int_beta=int(floor((beta/(pbeta*2.0))+0.5));//層數
55     float count_beta=(beta/(pbeta*2.0));
56     //int index_beta=int(floor(count_beta+ max_floorf+0.5));//整數層數索引
57     float index_beta=floor(count_beta+ max_floorf+0.5);
58     //int roomcount=0;
59     //使用數據紋理法,取這一層的區分度
60     //int int1=int(floor(mod(index_beta,4.0)+0.5));//使用哪一個顏色份量
61     //float float1=(index_beta/4.0);//在紋理採樣器中的順序索引
62     float floatu=(mod(index_beta,wid_utexturepalpha))/(wid_utexturepalpha)+(0.5/wid_utexturepalpha);//猜想u是x軸
63     float floatv=(((index_beta)/(wid_utexturepalpha)))/(hei_utexturepalpha)+(0.5/(wid_utexturepalpha*hei_utexturepalpha));
64     vec2 UVpalpha=vec2(floatu,floatv);
65     vec4 vec4palphas=texture2D(utexturepalpha, UVpalpha);
66     float palpha=(vec4palphas[0]*255.0*100.0+vec4palphas[1]*255.0)/pow(10.0,vec4palphas[2]*255.0);
67     //float palpha=getData500(uarrpalpha,int(floor(index_beta+0.5)));//改成嘗試數組法傳遞數據
68     //取這一層的轉角
69     float alpha=atan(oPosition.z,oPosition.x);//標準反正切函數有兩個參數!!
70 
71     if(getOdevity(index_beta)==1.0)//爲了體現交錯效果,若是是奇數層alpha要減去palpha
72     {
73         alpha-=palpha;
74     }
75     if(alpha<0.0)
76     {
77         alpha+=(MathPI*2.0);
78     }
79     //取地區塊數據紋理的索引
80     float floatu2=(alpha/(palpha*2.0))/wid_utexturedqk;
81     float floatv2=index_beta/hei_utexturedqk+0.5/hei_utexturedqk;
82     vec2 UVdqk=vec2(floatu2,floatv2);
83     gl_FragColor=texture2D(utexturedqk, UVdqk);
84     //gl_FragColor=vec4palphas;
85     //gl_FragColor=texelFetch(utexturedqk,ivec2(int(floor(alpha/(palpha*2.0)+0.5)),int(floor(index_beta+0.5))),0);//這個整數像素的方法是WebGL2開始加入的!!!!
86     //gl_FragColor=vec4(1.0*floatu,1.0*floatv,1.0*floatv2,1.0);//紅,綠,藍結果不是0就是1??!!
87 
88     //int index_dqk=roomcount-1+int(floor((alpha/palpha)+0.5));
89     //vec4 tempColor=vec4(uarrdqk[index_dqk],1.0);
90 
91     //float float_3=index_beta/(max_floorf*2.0);
92     //float float_4=oPosition.y/5.0;
93     //canvas的imagedata用255,255,255,1定義顏色通道,而glsl用1.0,1.0,1.0,1.0定義!!!!
94 
95 
96 }
View Code

片元着色器和前面的區別是加了一個判斷奇偶層數的方法,另外,在WebGL2.0中加入的texelFetch方法能夠直接根據像素的位置對紋理圖進行採樣,比使用紋理座標進行採樣方便許多。

執行程序效果以下:

拉近相機:

其中生成地形耗時1分30秒,平滑地形併入庫耗時3分10秒,從數據庫查詢須要約1分鐘

5、總結

這個方法耗時比較長,沒法實時應用,但能夠用來一次性構建地形屢次使用,也許能夠經過優化代碼來提升運行速度。生成的海洋和陸地混雜度比較高,也許能夠考慮從種子位置隨機生長地形的算法,這樣可使得大陸和海洋區分的更加分明。着色器代碼中沒有考慮週期性光照效果,也沒有考慮極點處的地區塊如何處理。

下一步準備給地區塊添加鼠標交互,和更細節的地貌塊顯示。

相關文章
相關標籤/搜索