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

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,5000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
简介: 在本文中,我将分享我们应对这些有趣挑战之一的方法:一种创建逼真的大型地形的简单方法。
推荐:使用 NSDT场景编辑器快速搭建3D应用场景

建 模 和 3D 地形

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

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

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

image.png

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

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

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

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

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

image.png

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


image.png

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

解码高度图

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

ar ground = BABYLON.Mesh.CreateGroundFromHeightMap(

'your-mesh-name',

'/path/to/heightmap.png',

100, // width of the ground mesh (x axis)

100, // depth of the ground mesh (z axis)

40, // number of subdivisions

0, // min height

50, // max height

scene,

false, // updateable?

null // callback when mesh is ready

);`

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

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

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

// simple wireframe material

var material = new BABYLON.StandardMaterial('ground-material', scene);

material.wireframe = true;

ground.material = material;`

创建纹理细节

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

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

image.png

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

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

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

image.png

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

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

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

顶点着色器

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

precision mediump float;

// Attributes

attribute vec3 position;

attribute vec3 normal;

attribute vec2 uv;

// Uniforms

uniform mat4 worldViewProjection;

// Varying

varying vec4 vPosition;

varying vec3 vNormal;

varying vec2 vUV;

void main() {

vec4 p = vec4( position, 1.0 );

vPosition = p;

vNormal = normal;

vUV = uv;

gl_Position = worldViewProjection * p;

}

碎片着色器

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

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

// #ifdef GL_ES

precision highp float;

// #endif

uniform mat4 worldView;

varying vec4 vPosition;

varying vec3 vNormal;

varying vec2 vUV;

// Refs

uniform sampler2D baseSampler;

uniform sampler2D blendSampler;

uniform float blendScaleU;

uniform float blendScaleV;

// #define FOGMODE_NONE 0.

// #define FOGMODE_EXP 1.

// #define FOGMODE_EXP2 2.

// #define FOGMODE_LINEAR 3.

// #define E 2.71828

uniform vec4 vFogInfos;

uniform vec3 vFogColor;

float calcFogFactor() {

// gets distance from camera to vertex

float fogDistance = gl_FragCoord.z / gl_FragCoord.w;

float fogCoeff = 1.0;

float fogStart = vFogInfos.y;

float fogEnd = vFogInfos.z;

float fogDensity = vFogInfos.w;

if (FOGMODE_LINEAR == vFogInfos.x) {

fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);

}

else if (FOGMODE_EXP == vFogInfos.x) {

fogCoeff = 1.0 / pow(E, fogDistance * fogDensity);

}

else if (FOGMODE_EXP2 == vFogInfos.x) {

fogCoeff = 1.0 / pow(E, fogDistance * fogDistance * fogDensity * fogDensity);

}

return clamp(fogCoeff, 0.0, 1.0);

}

void main(void) {

vec4 baseColor = texture2D(baseSampler, vUV);

vec2 blendUV = vec2(vUV.x * blendScaleU, vUV.y * blendScaleV);

vec4 blendColor = texture2D(blendSampler, blendUV);

// multiply type blending mode

vec4 color = baseColor * blendColor;

// factor in fog color

float fog = calcFogFactor();

color.rgb = fog * color.rgb + (1.0 - fog) * vFogColor;

gl_FragColor = color;

}

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

function BlendMaterial(name, scene, options) {

this.name = name;

this.id = name;

this.options = options;

this.blendScaleU = options.blendScaleU || 1;

this.blendScaleV = options.blendScaleV || 1;

this._scene = scene;

scene.materials.push(this);

var assets = options.assetManager;

var textureTask = assets.addTextureTask('blend-material-base-task', options.baseImage);

textureTask.onSuccess = _.bind(function(task) {

this.baseTexture = task.texture;
    this.baseTexture.uScale = 1;
    this.baseTexture.vScale = 1;
    if (options.baseHasAlpha) {
        this.baseTexture.hasAlpha = true;
    }
}, this);
textureTask = assets.addTextureTask('blend-material-blend-task', options.blendImage);
textureTask.onSuccess = _.bind(function(task) {
    this.blendTexture = task.texture;
    this.blendTexture.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE;
    this.blendTexture.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE;
}, this);

}

BlendMaterial.prototype = Object.create(BABYLON.Material.prototype);

BlendMaterial.prototype.needAlphaBlending = function () {

return (this.options.baseHasAlpha === true);

};

BlendMaterial.prototype.needAlphaTesting = function () {

return false;

};

BlendMaterial.prototype.isReady = function (mesh) {

var engine = this._scene.getEngine();

// make sure textures are ready

if (!this.baseTexture || !this.blendTexture) {

return false;

}

if (!this._effect) {

this._effect = engine.createEffect(

// shader name

"blend",

// attributes describing topology of vertices

[ "position", "normal", "uv" ],

// uniforms (external variables) defined by the shaders

[ "worldViewProjection", "world", "blendScaleU", "blendScaleV", "vFogInfos", "vFogColor" ],

// samplers (objects used to read textures)

[ "baseSampler", "blendSampler" ],

// optional define string

"");

}

if (!this._effect.isReady()) {

return false;

}

return true;

};

BlendMaterial.prototype.bind = function (world, mesh) {

var scene = this._scene;

this._effect.setFloat4("vFogInfos", scene.fogMode, scene.fogStart, scene.fogEnd, scene.fogDensity);

this._effect.setColor3("vFogColor", scene.fogColor);

this._effect.setMatrix("world", world);

this._effect.setMatrix("worldViewProjection", world.multiply(scene.getTransformMatrix()));

// Textures

this._effect.setTexture("baseSampler", this.baseTexture);

this._effect.setTexture("blendSampler", this.blendTexture);

this._effect.setFloat("blendScaleU", this.blendScaleU);

this._effect.setFloat("blendScaleV", this.blendScaleV);

};

BlendMaterial.prototype.dispose = function () {

if (this.baseTexture) {

this.baseTexture.dispose();

}

if (this.blendTexture) {

this.blendTexture.dispose();

}

this.baseDispose();

};

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


原文链接:使用 WebGL 为 HTML5 游戏创建逼真的地形

目录
相关文章
|
6月前
|
移动开发 HTML5
html5掷骰子跳棋游戏源码
html5掷骰子跳棋游戏源码
125 1
html5掷骰子跳棋游戏源码
|
2月前
|
移动开发 JavaScript 数据管理
HTML5 拖放在游戏中的应用
HTML5的拖放功能在游戏开发中广泛应用,尤其在创建交互式网页游戏时。它支持多种场景,如拖动角色或物品、选择和装备物品、拼图或配对游戏以及自定义界面布局。通过简单的HTML和JavaScript代码,可实现流畅的拖放交互,并提供视觉反馈,增强用户体验。此外,还需考虑设备兼容性和数据管理,确保游戏在不同设备和浏览器上都能良好运行。总之,HTML5拖放功能使网页游戏更生动有趣。
|
5月前
|
移动开发 前端开发 程序员
程序员必知:基于HTML5堆木头游戏
程序员必知:基于HTML5堆木头游戏
30 0
|
5月前
|
移动开发 监控 前端开发
基于 HTML5 WebGL 和 VR 技术的 3D 机房数据中心可视化
基于 HTML5 WebGL 和 VR 技术的 3D 机房数据中心可视化
|
5月前
切方块游戏 HTML5+jQuery【附源码】
切方块游戏 HTML5+jQuery【附源码】
38 0
|
6月前
|
移动开发 JavaScript 前端开发
webgl学习笔记3_javascript的HTML DOM
webgl学习笔记3_javascript的HTML DOM
64 0
webgl学习笔记3_javascript的HTML DOM
|
6月前
|
安全 Windows
在线网页版扫雷游戏HTML源码
在线网页版扫雷游戏HTML源码
217 1
在线网页版扫雷游戏HTML源码
|
Web App开发 移动开发 iOS开发
如何在浏览器中启用 WebGL 以使用 HTML5 3D 查看器
如何在浏览器中启用 WebGL 以使用 HTML5 3D 查看器
161 0
|
6月前
|
前端开发 JavaScript
html+css+js开发一个猜数字游戏
【1月更文挑战第5天】html+css+js开发一个猜数字游戏
111 1
|
6月前
|
JavaScript
基于js和html的骰子游戏
基于js和html的骰子游戏
82 0