使用 WebGL 为 HTML5 游戏创建逼真的地形

推荐:使用 NSDT场景编辑器快速搭建3D应用场景

建 模 和 3D 地形

大多数 3D 对象是 使用建模工具创建,这是有充分理由的。创建复杂对象 (如飞机甚至建筑物)很难在代码中完成。建模工具 几乎总是有意义的,但也有例外!其中之一可能是案例 就像飞行拱廊岛连绵起伏的丘陵一样。我们最终使用了 我们发现更简单,甚至可能更直观的技术:一个 高度图。

高度图是一种 使用常规二维图像来描述 像岛屿或其他地形一样的表面。这是一种非常常见的使用方式 高程数据,不仅在游戏中,而且在地理信息系统中 制图师和地质学家使用的 (GIS)。

帮助您获得想法 有关其工作原理,请查看此交互式演示中的高度图。尝试绘图 ,然后检出生成的地形。

高度图背后的概念 很简单。在上图所示的图像中,纯黑色是 “地板”和纯白色是最高峰。介于两者之间的灰度颜色 表示相应的高程。这为我们提供了 256 个海拔高度,这 是我们游戏的大量细节。实际应用程序可能会使用完整的 色谱可存储更多层次的细节(2564 = 4,294,967,296 级 详细信息(如果包含 Alpha 通道)。

高度图有几个 与传统多边形网格相比的优势:

一、高度图很多 更紧凑。仅存储最重要的数据(高程)。它 需要以编程方式转换为 3D 对象,但这是 经典交易:您现在节省空间,稍后通过计算付款。通过存储 数据即图像,您将获得另一个空间优势:您可以利用标准 图像压缩技术并使数据变小(相比之下)!

其次,高度图是一个 生成、可视化和编辑地形的便捷方式。非常直观 当你看到一个。感觉有点像看地图。这被证明是 对飞行街机特别有用。我们设计和编辑了我们的岛屿 在 Photoshop 中!这使得根据需要进行小调整变得非常简单。 例如,当我们想确保跑道完全平坦时, 我们只是确保以单一颜色在该区域上绘画。

您可以看到高度图 下面的飞行拱廊。看看你是否能发现我们为 跑道和村庄。

飞行街机的高度图

飞行街机岛的高度图。它是在Photoshop中创建的,它基于着名的太平洋岛链中的“大岛”。有什么猜测吗?

在解码高度贴图后映射到生成的 3D 网格上的纹理。更多内容见下文。

解码高度图

我们用Babylon.js建造了飞行拱廊,Babylon给了我们一个漂亮的 从高度图到 3D 的简单路径。Babylon提供了一个 API 来生成 来自高度图图像的网格几何体:

1
var ground = BABYLON.Mesh.CreateGroundFromHeightMap(
2
 
3
    'your-mesh-name',
4
 
5
    '/path/to/heightmap.png',
6
 
7
    100, // width of the ground mesh (x axis) 
8
 
9
    100, // depth of the ground mesh (z axis) 
10
 
11
    40,  // number of subdivisions 
12
 
13
    0,   // min height 
14
 
15
    50,  // max height 
16
 
17
    scene,
18
 
19
    false, // updateable? 
20
 
21
    null // callback when mesh is ready 
22
 
23
);

细节量是 由该细分的财产决定。需要注意的是, 参数是指高度图两侧的细分数量 图像,而不是单元格总数。所以稍微增加这个数字可以 对网格中的顶点总数有很大影响。

  • 20 个细分 = 400 细胞
  • 50 个细分 = 2,500 细胞
  • 100 个细分 = 10,000 细胞
  • 500 个细分 = 250,000 细胞
  • 1,000 个细分 = 1,000,000 细胞

在下一节中,我们将 了解如何为地面设置纹理,但在尝试使用高度贴图时 创建时,查看线框很有用。这是应用简单代码 线框纹理,因此很容易看到高度图数据是如何转换为的 网格的顶点:

1
// simple wireframe material 
2
 
3
var material = new BABYLON.StandardMaterial('ground-material', scene);
4
 
5
material.wireframe = true;
6
 
7
ground.material = material;

创建纹理细节

一旦我们有一个模型,映射一个 质地相对简单。对于飞行街机,我们简单地创建了一个 非常大的图像,与我们的高度图中的岛屿相匹配。图像得到 延伸到地形的轮廓上,所以纹理和高度图 保持相关性。这真的很容易想象,再一次,所有 制作工作是在Photoshop中完成的。

原始纹理图像是 创建于 4096x4096。那可是挺大的!(我们最终将尺寸减小了 为了保持下载合理,级别到2048x2048,但所有 使用全尺寸图像进行开发。这是来自 原始纹理。

原始岛屿纹理的全像素示例。整个城镇只有大约300平方像素。

这些矩形表示 岛上城镇的建筑。我们很快注意到 我们可以在地形和 其他 3D 模型。即使使用我们巨大的岛屿纹理,区别在于 令人分心的明显!

为了解决这个问题,我们“混合” 以随机噪声的形式进入地形纹理的附加细节。您可以 请参阅下面的之前和之后。注意额外的噪点如何增强外观 地形细节。

机场纹理前后对比

我们创建了一个自定义着色器 添加噪音。着色器为您提供了对 WebGL 3D 场景的渲染,这是着色器如何 有用。

WebGL着色器由两个组成 主要部分:顶点和片段着色器。顶点的主要目标 着色器是将顶点映射到渲染帧中的某个位置。片段(或 像素)着色器控制像素的结果颜色。

着色器是用 称为GLSL(图形库着色器语言)的高级语言,它 类似于C。此代码在 GPU 上执行。深入了解如何 着色器工作,请参阅此处 有关如何为 Babylon.js 创建自己的自定义着色器的教程,或参阅此图形着色器编码初学者指南。

顶点着色器

我们不会改变我们的 纹理映射到地面网格体,因此我们的顶点着色器非常简单。 它只是计算标准映射并分配目标位置。

1
precision mediump float;
2
 
3
 
4
 
5
// Attributes 
6
 
7
attribute vec3 position;
8
 
9
attribute vec3 normal;
10
 
11
attribute vec2 uv;
12
 
13
 
14
 
15
// Uniforms 
16
 
17
uniform mat4 worldViewProjection;
18
 
19
 
20
 
21
// Varying 
22
 
23
varying vec4 vPosition;
24
 
25
varying vec3 vNormal;
26
 
27
varying vec2 vUV;
28
 
29
 
30
 
31
void main() {
32
 
33
 
34
 
35
    vec4 p = vec4( position, 1.0 );
36
 
37
    vPosition = p;
38
 
39
    vNormal = normal;
40
 
41
    vUV = uv;
42
 
43
    gl_Position = worldViewProjection * p;
44
 
45
}

碎片着色器

我们的片段着色器有点 更复杂。它结合了两个不同的图像:基础图像和混合图像。 基础图像映射到整个地面网格。在飞行街机中,这个 是岛屿的彩色图像。混合图像是使用的小噪点图像 在近距离为地面提供一些纹理和细节。着色器 组合每个图像中的值以创建跨 岛。

飞行的最后一课 街机发生在有雾的日子,所以我们的像素着色器的另一个任务是 调整颜色以模拟雾。调整基于顶点的距离 来自相机,远处像素被“遮挡”得更厉害 在雾中。您将在函数中看到此距离计算 在主着色器代码上方。calcFogFactor

1
#ifdef GL_ES 
2
 
3
precision highp float;
4
 
5
#endif 
6
 
7
 
8
 
9
uniform mat4 worldView;
10
 
11
varying vec4 vPosition;
12
 
13
varying vec3 vNormal;
14
 
15
varying vec2 vUV;
16
 
17
 
18
 
19
// Refs 
20
 
21
uniform sampler2D baseSampler;
22
 
23
uniform sampler2D blendSampler;
24
 
25
uniform float blendScaleU;
26
 
27
uniform float blendScaleV;
28
 
29
 
30
 
31
#define FOGMODE_NONE 0. 
32
 
33
#define FOGMODE_EXP 1. 
34
 
35
#define FOGMODE_EXP2 2. 
36
 
37
#define FOGMODE_LINEAR 3. 
38
 
39
#define E 2.71828 
40
 
41
 
42
 
43
uniform vec4 vFogInfos;
44
 
45
uniform vec3 vFogColor;
46
 
47
 
48
 
49
float calcFogFactor() {
50
 
51
 
52
 
53
    // gets distance from camera to vertex 
54
 
55
    float fogDistance = gl_FragCoord.z / gl_FragCoord.w;
56
 
57
 
58
 
59
    float fogCoeff = 1.0;
60
 
61
    float fogStart = vFogInfos.y;
62
 
63
    float fogEnd = vFogInfos.z;
64
 
65
    float fogDensity = vFogInfos.w;
66
 
67
 
68
 
69
    if (FOGMODE_LINEAR == vFogInfos.x) {
70
 
71
        fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);
72
 
73
    }
74
 
75
    else if (FOGMODE_EXP == vFogInfos.x) {
76
 
77
        fogCoeff = 1.0 / pow(E, fogDistance * fogDensity);
78
 
79
    }
80
 
81
    else if (FOGMODE_EXP2 == vFogInfos.x) {
82
 
83
        fogCoeff = 1.0 / pow(E, fogDistance * fogDistance * fogDensity * fogDensity);
84
 
85
    }
86
 
87
 
88
 
89
    return clamp(fogCoeff, 0.0, 1.0);
90
 
91
}
92
 
93
 
94
 
95
void main(void) {
96
 
97
 
98
 
99
    vec4 baseColor = texture2D(baseSampler, vUV);
100
 
101
 
102
 
103
    vec2 blendUV = vec2(vUV.x * blendScaleU, vUV.y * blendScaleV);
104
 
105
    vec4 blendColor = texture2D(blendSampler, blendUV);
106
 
107
 
108
 
109
    // multiply type blending mode 
110
 
111
    vec4 color = baseColor * blendColor;
112
 
113
 
114
 
115
    // factor in fog color 
116
 
117
    float fog = calcFogFactor();
118
 
119
    color.rgb = fog * color.rgb + (1.0 - fog) * vFogColor;
120
 
121
 
122
 
123
    gl_FragColor = color;
124
 
125
}

我们定制的最后一件作品 Blend shader 是 Babylon 使用的 JavaScript 代码。主要目的 此代码用于准备传递给顶点和像素着色器的参数。

1
function BlendMaterial(name, scene, options) {
2
 
3
    this.name = name;
4
 
5
    this.id = name;
6
 
7
 
8
 
9
    this.options = options;
10
 
11
    this.blendScaleU = options.blendScaleU || 1;
12
 
13
    this.blendScaleV = options.blendScaleV || 1;
14
 
15
 
16
 
17
    this._scene = scene;
18
 
19
    scene.materials.push(this);
20
 
21
 
22
 
23
    var assets = options.assetManager;
24
 
25
    var textureTask = assets.addTextureTask('blend-material-base-task', options.baseImage);
26
 
27
    textureTask.onSuccess = _.bind(function(task) {
28
 
29
 
30
 
31
        this.baseTexture = task.texture;
32
 
33
        this.baseTexture.uScale = 1;
34
 
35
        this.baseTexture.vScale = 1;
36
 
37
 
38
 
39
        if (options.baseHasAlpha) {
40
 
41
            this.baseTexture.hasAlpha = true;
42
 
43
        }
44
 
45
 
46
 
47
    }, this);
48
 
49
 
50
 
51
    textureTask = assets.addTextureTask('blend-material-blend-task', options.blendImage);
52
 
53
    textureTask.onSuccess = _.bind(function(task) {
54
 
55
        this.blendTexture = task.texture;
56
 
57
        this.blendTexture.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
58
 
59
        this.blendTexture.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
60
 
61
    }, this);
62
 
63
 
64
 
65
}
66
 
67
 
68
 
69
BlendMaterial.prototype = Object.create(BABYLON.Material.prototype);
70
 
71
 
72
 
73
BlendMaterial.prototype.needAlphaBlending = function () {
74
 
75
    return (this.options.baseHasAlpha === true);
76
 
77
};
78
 
79
 
80
 
81
BlendMaterial.prototype.needAlphaTesting = function () {
82
 
83
    return false;
84
 
85
};
86
 
87
 
88
 
89
BlendMaterial.prototype.isReady = function (mesh) {
90
 
91
    var engine = this._scene.getEngine();
92
 
93
 
94
 
95
    // make sure textures are ready 
96
 
97
    if (!this.baseTexture || !this.blendTexture) {
98
 
99
        return false;
100
 
101
    }
102
 
103
 
104
 
105
    if (!this._effect) {
106
 
107
        this._effect = engine.createEffect(
108
 
109
 
110
 
111
            // shader name 
112
 
113
            "blend",
114
 
115
 
116
 
117
            // attributes describing topology of vertices 
118
 
119
            [ "position", "normal", "uv" ],
120
 
121
 
122
 
123
            // uniforms (external variables) defined by the shaders 
124
 
125
            [ "worldViewProjection", "world", "blendScaleU", "blendScaleV", "vFogInfos", "vFogColor" ],
126
 
127
 
128
 
129
            // samplers (objects used to read textures) 
130
 
131
            [ "baseSampler", "blendSampler" ],
132
 
133
 
134
 
135
            // optional define string 
136
 
137
            "");
138
 
139
    }
140
 
141
 
142
 
143
    if (!this._effect.isReady()) {
144
 
145
        return false;
146
 
147
    }
148
 
149
 
150
 
151
    return true;
152
 
153
};
154
 
155
 
156
 
157
BlendMaterial.prototype.bind = function (world, mesh) {
158
 
159
 
160
 
161
    var scene = this._scene;
162
 
163
    this._effect.setFloat4("vFogInfos", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);
164
 
165
    this._effect.setColor3("vFogColor", scene.fogColor);
166
 
167
 
168
 
169
    this._effect.setMatrix("world", world);
170
 
171
    this._effect.setMatrix("worldViewProjection", world.multiply(scene.getTransformMatrix()));
172
 
173
 
174
 
175
    // Textures 
176
 
177
    this._effect.setTexture("baseSampler", this.baseTexture);
178
 
179
    this._effect.setTexture("blendSampler", this.blendTexture);
180
 
181
 
182
 
183
    this._effect.setFloat("blendScaleU", this.blendScaleU);
184
 
185
    this._effect.setFloat("blendScaleV", this.blendScaleV);
186
 
187
};
188
 
189
 
190
 
191
BlendMaterial.prototype.dispose = function () {
192
 
193
 
194
 
195
    if (this.baseTexture) {
196
 
197
        this.baseTexture.dispose();
198
 
199
    }
200
 
201
 
202
 
203
    if (this.blendTexture) {
204
 
205
        this.blendTexture.dispose();
206
 
207
    }
208
 
209
 
210
 
211
    this.baseDispose();
212
 
213
};

Babylon.js使它变得容易 创建基于着色器的自定义材质。我们的混合材料相对简单, 但它确实对岛屿的外观产生了很大的影响,当 飞机低空飞到地面。着色器将 GPU 的强大功能带到 浏览器,扩展可应用于 3D 的创意效果类型 场景。在我们的案例中,这是画龙点名!

原文链接:使用 WebGL 为 HTML5 游戏创建逼真的地形 (mvrlink.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/123156.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ant-table组件表格数据做合计行,并固定在表格底部

需求描述 某些时候,我们表格展示数值型数据的时候,可能会想做一个合计处理,这样能直观的了解到当前列的总计数据信息。 遍观Table组件,官方是没有提供这个功能的,这就需要我们自己处理了 需求分析 根据表格的特性&a…

Java环境的安装

最近博主也是在学校开始学习了Java,也通过老师知道了可以通过大学生学生证申(bai)请(piao) IDEA的企业版(社区版也是够学习用的)有很多同学还是没有搞懂便做一下分享。 🌱博客主页:青竹雾色间. 😘博客制作…

算法笔记——路径问题

在引入介绍如何写一个算法的时候,我们先引入一个题作为例子 1137. 第 N 个泰波那契数 - 力扣(LeetCode) 作为刚开始学习算法的我们,看到这个题目的时候,应该想好以下的问题: 1.状态表示 我们要用什么来表…

MFC新建内部消息

提示:记录一下MFC新建内部消息的成功过程 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 先说一下基本情况,因为要在mapview上增加一个显示加载时间的功能。然后发现是要等加载完再显示时间,显示在主…

整理mongodb文档:分页

个人博客 整理mongodb文档:分页 个人博客,求关注,如果文章不够清晰,麻烦指出。 文章概叙 本文主要讲下在聚合以及crud的find方法中如何使用limit还有skip进行排序。 分页的情况很经常出现,这也是这篇博客诞生的理由。 数据准备…

uniapp移动端h5设计稿还原

思路 动态设置html的font-size大小 实现步骤 先创建一个public.css文件,设置初始的font-size大小 /* 注意这样写 只能使用css文件, scss 是不支持的, setProperty 只适用于原生css上 */ html {--gobal-font-size: 0.45px; } .gobal-font-size {font-size: var(--g…

面试中的身体语言:非语言信息的重要性

🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…

Termux配置bashrc,终端长路径改为短路径

Termux配置bashrc termux的bashrc在$HOME/../usr/etc目录下 先使用cd命令进入用户主目录 然后cd ../usr/etc,ls可以发现,存在一个名为bash.bashrc的文件 使用vim或者其它编辑器打开 大概在第15行 PS1\[\e[0;32m\]\w\[\e[0m\] \[\e[0;97m\]\$\[\e[0…

六安RapidSSL泛域名https能保护几个域名

RapidSSL是Geotrust旗下的子品牌,Geotrust是国际知名的CA认证机构,每年都要通过WebTrust年检,而Geotrust旗下的https证书已经应用于市场多年,Geotrust的子品牌RapidSSL证书经营的DV基础型泛域名https证书也受到市场的欢迎。今天就…

如何使用SpringCloud Eureka 创建单机Eureka Server-注册中心

😀前言 本篇博文是关于使用SpringCloud Eureka 创建单机Eureka Server-注册中心,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家&…

2022年12月 C/C++(八级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:生理周期 人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为23天、28天和33天。每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,智力周期的高峰,人会思维敏捷,精力容易高度集中。因…

【已解决】使用xshell来ssh到vmware的虚拟机,请求超时的问题

我的情况: 1.本地ping虚拟机请求超时,但是虚拟机ping本地成功 2.本地和虚拟机的防火墙都关了,ssh服务也开了 3.端口也是正确的 百思不得其解,不知道为什么就是连接不上 当出现这种情况的时候,可以考虑一下vmware的…

postgresql 安装教程

postgresql 安装教程 本文以window 15版本为教程 文章目录 postgresql 安装教程1.下载地址2.以管理员身份运行3.选择安装路径,点击Next4.选择组件(默认都勾选),点击Next5.选择数据存储路径,点击Next6.设置超级用户的…

Blazor前后端框架Known-V1.2.14

V1.2.14 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行。 Gitee: https://gitee.com/known/KnownGithub:https://github.com/known/Known 概述 基于C#和Blazo…

展厅显示大屏怎么选,展厅使用哪种显示大屏好?

近年来,随着信息化科技快速发展,越来越多的企事业单位的展厅都会采用显示大屏来展现各种信息。那么,展厅显示大屏怎么选,哪种显示大屏更好?接下来,小灰从专业角度,用通俗易懂的文字为大家逐一分…

【算法训练-链表 三】【判断】判断链表中是否有环、判断链表是否为回文链表

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【链表的相关判断】,使用【链表】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为…

通过 Blob 对二进制流文件下载实现文件保存下载

原理&#xff1a;前端将二进制文件做转换实现下载: 请求后端接口->接收后端返回的二进制流(通过二进制流&#xff08;Blob&#xff09;下载,把后端返回的二进制文件放在 Blob 里面)->再通过file-saver插件保存 页面上使用&#xff1a; <span click"downloadFil…

Python Opencv实践 - 图像的距(Moments,Hu Moments)

参考资料&#xff1a;​​​​​​矩特征---OpenCV-Python开发指南&#xff08;25&#xff09;_cv2.moments_李元静的博客-CSDN博客 探究opencv中的moments函数和HuMoments函数_opencv moment_傲笑风的博客-CSDN博客 import cv2 as cv import numpy as np import matplotlib.…

Scrum敏捷模式的优势点、实践经验及适用企业

Scrum敏捷模式是一种灵活、适应性强的开发方法&#xff0c;其核心理念是以短周期、高频率的方式进行项目开发&#xff0c;确保团队能够快速响应变化。 Scrum包含三个角色&#xff1a;产品负责人&#xff08;Product Owner&#xff09;、Scrum Master和开发团队&#xff08;Tea…

环信uni-app-demo 升级改造计划——单人多人音视频通话(三)

前序文章&#xff1a; 环信 uni-app Demo升级改造计划——Vue2迁移到Vue3&#xff08;一&#xff09; 环信即时通讯SDK集成——环信 uni-app-demo 升级改造计划——整体代码重构优化&#xff08;二&#xff09; 概述 在将声网 uni-app 音视频插件正式集成进入环信的 uni-app…