You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
learn-wgpu/intermediate/wip-terrain/index.html

130 lines
53 KiB
HTML

<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Procedural Terrain | Learn Wgpu</title>
<meta name="generator" content="VuePress 1.9.10">
<meta name="description" content="">
<link rel="preload" href="/learn-wgpu/assets/css/0.styles.81cb5453.css" as="style"><link rel="preload" href="/learn-wgpu/assets/js/app.8ee4646c.js" as="script"><link rel="preload" href="/learn-wgpu/assets/js/2.2ef7287a.js" as="script"><link rel="preload" href="/learn-wgpu/assets/js/1.eaeeb819.js" as="script"><link rel="preload" href="/learn-wgpu/assets/js/31.fbafea68.js" as="script"><link rel="prefetch" href="/learn-wgpu/assets/js/10.baf5d1fe.js"><link rel="prefetch" href="/learn-wgpu/assets/js/11.b8633c49.js"><link rel="prefetch" href="/learn-wgpu/assets/js/12.0b8835f1.js"><link rel="prefetch" href="/learn-wgpu/assets/js/13.af1bbe85.js"><link rel="prefetch" href="/learn-wgpu/assets/js/14.626c7cb3.js"><link rel="prefetch" href="/learn-wgpu/assets/js/15.5a8ea302.js"><link rel="prefetch" href="/learn-wgpu/assets/js/16.f4e8226b.js"><link rel="prefetch" href="/learn-wgpu/assets/js/17.ea2d3b52.js"><link rel="prefetch" href="/learn-wgpu/assets/js/18.4eb59bb5.js"><link rel="prefetch" href="/learn-wgpu/assets/js/19.48960e96.js"><link rel="prefetch" href="/learn-wgpu/assets/js/20.a01dc53f.js"><link rel="prefetch" href="/learn-wgpu/assets/js/21.5e81f33c.js"><link rel="prefetch" href="/learn-wgpu/assets/js/22.caf73498.js"><link rel="prefetch" href="/learn-wgpu/assets/js/23.ddff5db2.js"><link rel="prefetch" href="/learn-wgpu/assets/js/24.9ed2104b.js"><link rel="prefetch" href="/learn-wgpu/assets/js/25.1676676b.js"><link rel="prefetch" href="/learn-wgpu/assets/js/26.01a5696a.js"><link rel="prefetch" href="/learn-wgpu/assets/js/27.3b14c055.js"><link rel="prefetch" href="/learn-wgpu/assets/js/28.d1a4ff6c.js"><link rel="prefetch" href="/learn-wgpu/assets/js/29.ea2d6bbf.js"><link rel="prefetch" href="/learn-wgpu/assets/js/3.80d94795.js"><link rel="prefetch" href="/learn-wgpu/assets/js/30.f08bd92d.js"><link rel="prefetch" href="/learn-wgpu/assets/js/32.3d0e544d.js"><link rel="prefetch" href="/learn-wgpu/assets/js/33.945dce62.js"><link rel="prefetch" href="/learn-wgpu/assets/js/34.0fe8e5e4.js"><link rel="prefetch" href="/learn-wgpu/assets/js/35.7c607f94.js"><link rel="prefetch" href="/learn-wgpu/assets/js/36.2e1d433e.js"><link rel="prefetch" href="/learn-wgpu/assets/js/37.5ecf7ce2.js"><link rel="prefetch" href="/learn-wgpu/assets/js/38.b0253733.js"><link rel="prefetch" href="/learn-wgpu/assets/js/39.73123b03.js"><link rel="prefetch" href="/learn-wgpu/assets/js/4.ff267fdb.js"><link rel="prefetch" href="/learn-wgpu/assets/js/40.30864a0f.js"><link rel="prefetch" href="/learn-wgpu/assets/js/41.0b7d478f.js"><link rel="prefetch" href="/learn-wgpu/assets/js/42.4caac867.js"><link rel="prefetch" href="/learn-wgpu/assets/js/43.991fa7d2.js"><link rel="prefetch" href="/learn-wgpu/assets/js/44.27513d85.js"><link rel="prefetch" href="/learn-wgpu/assets/js/45.2ea5b108.js"><link rel="prefetch" href="/learn-wgpu/assets/js/46.61045c81.js"><link rel="prefetch" href="/learn-wgpu/assets/js/47.ea92b080.js"><link rel="prefetch" href="/learn-wgpu/assets/js/48.cc620f49.js"><link rel="prefetch" href="/learn-wgpu/assets/js/49.6426e080.js"><link rel="prefetch" href="/learn-wgpu/assets/js/5.250d79d3.js"><link rel="prefetch" href="/learn-wgpu/assets/js/50.05fd19ce.js"><link rel="prefetch" href="/learn-wgpu/assets/js/51.47410096.js"><link rel="prefetch" href="/learn-wgpu/assets/js/52.ecbe158e.js"><link rel="prefetch" href="/learn-wgpu/assets/js/53.8c976652.js"><link rel="prefetch" href="/learn-wgpu/assets/js/54.daee63ce.js"><link rel="prefetch" href="/learn-wgpu/assets/js/55.3799ddaa.js"><link rel="prefetch" href="/learn-wgpu/assets/js/56.74e5dcef.js"><link rel="prefetch" href="/learn-wgpu/assets/js/57.9cdfcb79.js"><link rel="prefetch" href="/learn-wgpu/assets/js/58.35b75a7b.js"><link rel="prefetch" href="/learn-wgpu/assets/js/59.5dd82339.js"><link rel="prefetch" href="/learn-wgpu/assets/js/60.1ced8124.js"><link rel="prefetch" href="/learn-wgpu/assets/js/61.f10516d3.js"><link rel="prefetch" href="/learn-wgpu/assets/js/62.fc721fd9.js"><link rel="prefetch" href="/learn-wgpu/assets/js/63.68526f40.js"><link rel="prefetch" href="/learn-wgpu/assets/js/64.b9a310af.js"><link rel="prefetch" href="/learn-wgpu/assets/js/65.e23a076d.js"><link rel="prefetch" href="/learn-wgpu/assets/js/66.19a033d1.js"><link rel="prefetch" href="/learn-wgpu/assets/js/67.ab338058.js"><link rel="prefetch" href="/learn-wgpu/assets/js/68.d785413d.js"><link rel="prefetch" href="/learn-wgpu/assets/js/8.2c0d831a.js"><link rel="prefetch" href="/learn-wgpu/assets/js/9.985bc61a.js"><link rel="prefetch" href="/learn-wgpu/assets/js/vendors~docsearch.efa6f8ef.js">
<link rel="stylesheet" href="/learn-wgpu/assets/css/0.styles.81cb5453.css">
</head>
<body>
<div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="inner"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/learn-wgpu/" class="home-link router-link-active"><!----> <span class="site-name">Learn Wgpu</span></a> <div class="links"><!----> <div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div></div></div></header> <div class="sidebar-mask"></div> <div class="docs-layout"><aside class="sidebar"><!----> <ul class="sidebar-links"><li><a href="/learn-wgpu/" aria-current="page" class="sidebar-link">Introduction</a></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>Beginner</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/learn-wgpu/beginner/tutorial1-window/" class="sidebar-link">Dependencies and the window</a></li><li><a href="/learn-wgpu/beginner/tutorial2-surface/" class="sidebar-link">The Surface</a></li><li><a href="/learn-wgpu/beginner/tutorial3-pipeline/" class="sidebar-link">The Pipeline</a></li><li><a href="/learn-wgpu/beginner/tutorial4-buffer/" class="sidebar-link">Buffers and Indices</a></li><li><a href="/learn-wgpu/beginner/tutorial5-textures/" class="sidebar-link">Textures and bind groups</a></li><li><a href="/learn-wgpu/beginner/tutorial6-uniforms/" class="sidebar-link">Uniform buffers and a 3d camera</a></li><li><a href="/learn-wgpu/beginner/tutorial7-instancing/" class="sidebar-link">Instancing</a></li><li><a href="/learn-wgpu/beginner/tutorial8-depth/" class="sidebar-link">The Depth Buffer</a></li><li><a href="/learn-wgpu/beginner/tutorial9-models/" class="sidebar-link">Model Loading</a></li></ul></section></li><li><section class="sidebar-group depth-0"><p class="sidebar-heading"><span>Intermediate</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/learn-wgpu/intermediate/tutorial10-lighting/" class="sidebar-link">Working with Lights</a></li><li><a href="/learn-wgpu/intermediate/tutorial11-normals/" class="sidebar-link">Normal Mapping</a></li><li><a href="/learn-wgpu/intermediate/tutorial12-camera/" class="sidebar-link">A Better Camera</a></li><li><a href="/learn-wgpu/intermediate/tutorial13-hdr/" class="sidebar-link">High Dynamic Range Rendering</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading"><span>Showcase</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading"><span>News</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="procedural-terrain"><a href="#procedural-terrain" class="header-anchor">#</a> Procedural Terrain</h1> <p>Up to this point we've been working in an empty void. This is great when you want to get your shading code just right, but most applications will want to fill the screen more interesting things. You could aproach this in a variety of ways. You could create a bunch of models in Blender and load them into the scene. This method works great if you have some decent artistic skills, and some patience. I'm lacking in both those departments, so let's write some code to make something that looks nice.</p> <p>As the name of this article suggests we're going to create a terrain. Now the traditional method to create a terrain mesh is to use a pre-generated noise texture and sampling it to get the height values at each point in the mesh. This is a perfectly valid way to approach this, but I opted to generate the noise using a Compute Shader directly. Let's get started!</p> <h2 id="compute-shaders"><a href="#compute-shaders" class="header-anchor">#</a> Compute Shaders</h2> <p>A compute shader is simply a shader that allows you to leverage the GPU's parallel computing power for arbitrary tasks. You can use them for anything from creating a texture to running a neural network. I'll get more into how they work in a bit, but for now suffice to say that we're going to use them to create the vertex and index buffers for our terrain.</p> <div class="note"><p>As of writing, compute shaders are still experimental on the web. You can enable them on beta versions of browsers such as Chrome Canary and Firefox Nightly. Because of this I'll cover a method to use a fragment shader to compute the vertex and index buffers after we cover the compute shader method.</p></div> <h2 id="noise-functions"><a href="#noise-functions" class="header-anchor">#</a> Noise Functions</h2> <p>Lets start with the shader code for the compute shader. First we'll create the noise functions, then we'll create the compute shader's entry function. Create a new file called <code>terrain.wgsl</code>. Then add the following:</p> <div class="language-wgsl extra-class"><pre class="language-wgsl"><code><span class="token comment">// ============================</span>
<span class="token comment">// Terrain Generation</span>
<span class="token comment">// ============================</span>
<span class="token comment">// https://gist.github.com/munrocket/236ed5ba7e409b8bdf1ff6eca5dcdc39</span>
<span class="token comment">// MIT License. © Ian McEwan, Stefan Gustavson, Munrocket</span>
<span class="token comment">// - Less condensed glsl implementation with comments can be found at https://weber.itn.liu.se/~stegu/jgt2012/article.pdf</span>
<span class="token keyword">fn</span> <span class="token functions function">permute3</span><span class="token punctuation">(</span>x<span class="token punctuation">:</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">(</span>x <span class="token operator">*</span> <span class="token decimal-float-literal number">34.</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token decimal-float-literal number">1.</span><span class="token punctuation">)</span> <span class="token operator">*</span> x<span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">289.</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">fn</span> <span class="token functions function">snoise2</span><span class="token punctuation">(</span>v<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token builtin">f32</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> <span class="token class-name">C</span> <span class="token operator">=</span> <span class="token builtin">vec4</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.211324865405187</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.366025403784439</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token decimal-float-literal number">0.577350269189626</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.024390243902439</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> i<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span> <span class="token operator">=</span> <span class="token builtin">floor</span><span class="token punctuation">(</span>v <span class="token operator">+</span> <span class="token builtin">dot</span><span class="token punctuation">(</span>v<span class="token punctuation">,</span> <span class="token class-name">C</span><span class="token punctuation">.</span>yy<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> x0 <span class="token operator">=</span> v <span class="token operator">-</span> i <span class="token operator">+</span> <span class="token builtin">dot</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> <span class="token class-name">C</span><span class="token punctuation">.</span>xx<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// I flipped the condition here from &gt; to &lt; as it fixed some artifacting I was observing</span>
<span class="token keyword">var</span> i1<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span> <span class="token operator">=</span> <span class="token builtin">select</span><span class="token punctuation">(</span><span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">1.</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">1.</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>x<span class="token decimal-float-literal number">0.</span>x <span class="token punctuation">&lt;</span> x<span class="token decimal-float-literal number">0.</span>y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> x12<span class="token punctuation">:</span> <span class="token builtin">vec4</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span> <span class="token operator">=</span> x<span class="token decimal-float-literal number">0.</span>xyxy <span class="token operator">+</span> <span class="token class-name">C</span><span class="token punctuation">.</span>xxzz <span class="token operator">-</span> <span class="token builtin">vec4</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span>i1<span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
i <span class="token operator">=</span> i <span class="token operator">%</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">289.</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> p <span class="token operator">=</span> <span class="token function-calls function">permute3</span><span class="token punctuation">(</span><span class="token function-calls function">permute3</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span>y <span class="token operator">+</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.</span><span class="token punctuation">,</span> i<span class="token decimal-float-literal number">1.</span>y<span class="token punctuation">,</span> <span class="token decimal-float-literal number">1.</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> i<span class="token punctuation">.</span>x <span class="token operator">+</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.</span><span class="token punctuation">,</span> i<span class="token decimal-float-literal number">1.</span>x<span class="token punctuation">,</span> <span class="token decimal-float-literal number">1.</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> m<span class="token punctuation">:</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span> <span class="token operator">=</span> <span class="token builtin">max</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.5</span> <span class="token operator">-</span>
<span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token builtin">dot</span><span class="token punctuation">(</span>x0<span class="token punctuation">,</span> x0<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">dot</span><span class="token punctuation">(</span>x<span class="token decimal-float-literal number">12.</span>xy<span class="token punctuation">,</span> x<span class="token decimal-float-literal number">12.</span>xy<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">dot</span><span class="token punctuation">(</span>x<span class="token decimal-float-literal number">12.</span>zw<span class="token punctuation">,</span> x<span class="token decimal-float-literal number">12.</span>zw<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
m <span class="token operator">=</span> m <span class="token operator">*</span> m<span class="token punctuation">;</span>
m <span class="token operator">=</span> m <span class="token operator">*</span> m<span class="token punctuation">;</span>
<span class="token keyword">let</span> x <span class="token operator">=</span> <span class="token decimal-float-literal number">2.</span> <span class="token operator">*</span> <span class="token builtin">fract</span><span class="token punctuation">(</span>p <span class="token operator">*</span> <span class="token class-name">C</span><span class="token punctuation">.</span>www<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token decimal-float-literal number">1.</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> h <span class="token operator">=</span> <span class="token builtin">abs</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token decimal-float-literal number">0.5</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> ox <span class="token operator">=</span> <span class="token builtin">floor</span><span class="token punctuation">(</span>x <span class="token operator">+</span> <span class="token decimal-float-literal number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> a0 <span class="token operator">=</span> x <span class="token operator">-</span> ox<span class="token punctuation">;</span>
m <span class="token operator">=</span> m <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token decimal-float-literal number">1.79284291400159</span> <span class="token operator">-</span> <span class="token decimal-float-literal number">0.85373472095314</span> <span class="token operator">*</span> <span class="token punctuation">(</span>a0 <span class="token operator">*</span> a0 <span class="token operator">+</span> h <span class="token operator">*</span> h<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> g <span class="token operator">=</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span>a<span class="token decimal-float-literal number">0.</span>x <span class="token operator">*</span> x<span class="token decimal-float-literal number">0.</span>x <span class="token operator">+</span> h<span class="token punctuation">.</span>x <span class="token operator">*</span> x<span class="token decimal-float-literal number">0.</span>y<span class="token punctuation">,</span> a<span class="token decimal-float-literal number">0.</span>yz <span class="token operator">*</span> x<span class="token decimal-float-literal number">12.</span>xz <span class="token operator">+</span> h<span class="token punctuation">.</span>yz <span class="token operator">*</span> x<span class="token decimal-float-literal number">12.</span>yw<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token decimal-float-literal number">130.</span> <span class="token operator">*</span> <span class="token builtin">dot</span><span class="token punctuation">(</span>m<span class="token punctuation">,</span> g<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>Some of my readers may recognize this as an implementation of Simplex noise (specifically OpenSimplex noise). I'll admit to not really understanding the math behind OpenSimplex noise. The basics of it are that it's similar to Perlin Noise, but instead of a square grid it's a hexagonal grid which removes some of the artifacts that generating the noise on a square grid gets you. Again I'm not an expert on this, so to summarize: <code>permute3()</code> takes a <code>vec3</code> and returns a pseudorandom <code>vec3</code>, <code>snoise2()</code> takes a <code>vec2</code> and returns a floating point number between [-1, 1]. If you want to learn more about noise functions, check out <a href="https://thebookofshaders.com/11/" target="_blank" rel="noopener noreferrer">this article from The Book of Shaders<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>. The code's in GLSL, but the concepts are the same.</p> <p>While we can use the output of <code>snoise</code> directly to generate the terrains height values. The result of this tends to be very smooth, which may be what you want, but doesn't look very organic as you can see below:</p> <p><img src="/learn-wgpu/assets/img/figure_no-fbm.1931fe05.png" alt="smooth terrain"></p> <p>To make the terrain a bit rougher we're going to use a technique called <a href="https://thebookofshaders.com/13/" target="_blank" rel="noopener noreferrer">Fractal Brownian Motion<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>. This technique works by sampling the noise function multiple times cutting the strength in half each time while doubling the frequency of the noise. This means that the overall shape of the terrain will be fairly smooth, but it will have sharper details. You can see what that will look like below:</p> <p><img src="/learn-wgpu/assets/img/figure_fbm.28310498.png" alt="more organic terrain"></p> <p>The code for this function is actually quite simple:</p> <div class="language-wgsl extra-class"><pre class="language-wgsl"><code><span class="token keyword">fn</span> <span class="token functions function">fbm</span><span class="token punctuation">(</span>p<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token builtin">f32</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> NUM_OCTAVES<span class="token punctuation">:</span> <span class="token builtin">u32</span> <span class="token operator">=</span> <span class="token int-literal number">5u</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> x <span class="token operator">=</span> p <span class="token operator">*</span> <span class="token decimal-float-literal number">0.01</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> v <span class="token operator">=</span> <span class="token decimal-float-literal number">0.0</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token decimal-float-literal number">0.5</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> shift <span class="token operator">=</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">100.0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> cs <span class="token operator">=</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token builtin">cos</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">sin</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> rot <span class="token operator">=</span> <span class="token builtin">mat2x2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span>cs<span class="token punctuation">.</span>x<span class="token punctuation">,</span> cs<span class="token punctuation">.</span>y<span class="token punctuation">,</span> <span class="token operator">-</span>cs<span class="token punctuation">.</span>y<span class="token punctuation">,</span> cs<span class="token punctuation">.</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i<span class="token operator">=</span><span class="token int-literal number">0u</span><span class="token punctuation">;</span> i<span class="token punctuation">&lt;</span>NUM_OCTAVES<span class="token punctuation">;</span> i<span class="token operator">=</span>i<span class="token operator">+</span><span class="token int-literal number">1u</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
v <span class="token operator">=</span> v <span class="token operator">+</span> a <span class="token operator">*</span> <span class="token function-calls function">snoise2</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>
x <span class="token operator">=</span> rot <span class="token operator">*</span> x <span class="token operator">*</span> <span class="token decimal-float-literal number">2.0</span> <span class="token operator">+</span> shift<span class="token punctuation">;</span>
a <span class="token operator">=</span> a <span class="token operator">*</span> <span class="token decimal-float-literal number">0.5</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> v<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>Let's go over some this a bit:</p> <ul><li>The <code>NUM_OCTAVES</code> constant is the number of levels of noise you want. More octaves will add more texture to the terrain mesh, but you'll get diminishing returns at higher levels. I find that 5 is a good number.</li> <li>We multiple <code>p</code> by <code>0.01</code> to &quot;zoom in&quot; on the noise function. This is because as our mesh will be 1x1 quads and the simplex noise function resembles white noise when stepping by one each time. You can see what that looks like to use <code>p</code> directly: <img src="/learn-wgpu/assets/img/figure_spiky.46babb7a.png" alt="spiky terrain"></li> <li>The <code>a</code> variable is the amplitude of the noise at the given noise level.</li> <li><code>shift</code> and <code>rot</code> are used to reduce artifacts in the generated noise. One such artiface is that at <code>0,0</code> the output of the <code>snoise</code> will always be the same regardless of how much you scale <code>p</code>.</li></ul> <h2 id="generating-the-mesh"><a href="#generating-the-mesh" class="header-anchor">#</a> Generating the mesh</h2> <p>To generate the terrain mesh we're going to need to pass some information into the shader:</p> <div class="language-wgsl extra-class"><pre class="language-wgsl"><code><span class="token keyword">struct</span> <span class="token class-name">ChunkData</span> <span class="token punctuation">{</span>
chunk_size<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">u32</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span>
chunk_corner<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">i32</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span>
min_max_height<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">struct</span> <span class="token class-name">Vertex</span> <span class="token punctuation">{</span>
<span class="token punctuation">@</span><span class="token attributes attr-name">location</span><span class="token punctuation">(</span><span class="token int-literal number">0</span><span class="token punctuation">)</span> position<span class="token punctuation">:</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span>
<span class="token punctuation">@</span><span class="token attributes attr-name">location</span><span class="token punctuation">(</span><span class="token int-literal number">1</span><span class="token punctuation">)</span> normal<span class="token punctuation">:</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token keyword">struct</span> <span class="token class-name">VertexBuffer</span> <span class="token punctuation">{</span>
data<span class="token punctuation">:</span> <span class="token builtin">array</span><span class="token punctuation">&lt;</span><span class="token class-name">Vertex</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span> <span class="token comment">// stride: 32</span>
<span class="token punctuation">}</span>
<span class="token keyword">struct</span> <span class="token class-name">IndexBuffer</span> <span class="token punctuation">{</span>
data<span class="token punctuation">:</span> <span class="token builtin">array</span><span class="token punctuation">&lt;</span><span class="token builtin">u32</span><span class="token punctuation">&gt;</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token punctuation">@</span><span class="token attributes attr-name">group</span><span class="token punctuation">(</span><span class="token int-literal number">0</span><span class="token punctuation">)</span> <span class="token punctuation">@</span><span class="token attributes attr-name">binding</span><span class="token punctuation">(</span><span class="token int-literal number">0</span><span class="token punctuation">)</span> <span class="token keyword">var</span><span class="token punctuation">&lt;</span><span class="token keyword">uniform</span><span class="token punctuation">&gt;</span> chunk_data<span class="token punctuation">:</span> <span class="token class-name">ChunkData</span><span class="token punctuation">;</span>
<span class="token punctuation">@</span><span class="token attributes attr-name">group</span><span class="token punctuation">(</span><span class="token int-literal number">0</span><span class="token punctuation">)</span><span class="token punctuation">@</span><span class="token attributes attr-name">binding</span><span class="token punctuation">(</span><span class="token int-literal number">1</span><span class="token punctuation">)</span> <span class="token keyword">var</span><span class="token punctuation">&lt;</span><span class="token keyword">storage</span><span class="token punctuation">,</span> read_write<span class="token punctuation">&gt;</span> vertices<span class="token punctuation">:</span> <span class="token class-name">VertexBuffer</span><span class="token punctuation">;</span>
<span class="token punctuation">@</span><span class="token attributes attr-name">group</span><span class="token punctuation">(</span><span class="token int-literal number">0</span><span class="token punctuation">)</span><span class="token punctuation">@</span><span class="token attributes attr-name">binding</span><span class="token punctuation">(</span><span class="token int-literal number">2</span><span class="token punctuation">)</span> <span class="token keyword">var</span><span class="token punctuation">&lt;</span><span class="token keyword">storage</span><span class="token punctuation">,</span> read_write<span class="token punctuation">&gt;</span> indices<span class="token punctuation">:</span> <span class="token class-name">IndexBuffer</span><span class="token punctuation">;</span>
</code></pre></div><p>Our shader will expect a <code>uniform</code> buffer that includes the size of the quad grid in <code>chunk_size</code>, the <code>chunk_corner</code> that our noise algorithm should start at, and <code>min_max_height</code> of the terrain.</p> <p>The vertex and index buffers are passed in as <code>storage</code> buffers with <code>read_write</code> enabled. We'll create the actual buffers in Rust and bind them when we execute the compute shader.</p> <p>The next part of the shader will be the functions that generate a point on the mesh, and a vertex at that point:</p> <div class="language-wgsl extra-class"><pre class="language-wgsl"><code><span class="token keyword">fn</span> <span class="token functions function">terrain_point</span><span class="token punctuation">(</span>p<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span>
p<span class="token punctuation">.</span>x<span class="token punctuation">,</span>
<span class="token builtin">mix</span><span class="token punctuation">(</span>chunk_data<span class="token punctuation">.</span>min_max_height<span class="token punctuation">.</span>x<span class="token punctuation">,</span>chunk_data<span class="token punctuation">.</span>min_max_height<span class="token punctuation">.</span>y<span class="token punctuation">,</span> <span class="token function-calls function">fbm</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
p<span class="token punctuation">.</span>y<span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">fn</span> <span class="token functions function">terrain_vertex</span><span class="token punctuation">(</span>p<span class="token punctuation">:</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token class-name">Vertex</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> v <span class="token operator">=</span> <span class="token function-calls function">terrain_point</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> tpx <span class="token operator">=</span> <span class="token function-calls function">terrain_point</span><span class="token punctuation">(</span>p <span class="token operator">+</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.1</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span> v<span class="token punctuation">;</span>
<span class="token keyword">let</span> tpz <span class="token operator">=</span> <span class="token function-calls function">terrain_point</span><span class="token punctuation">(</span>p <span class="token operator">+</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.0</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span> v<span class="token punctuation">;</span>
<span class="token keyword">let</span> tnx <span class="token operator">=</span> <span class="token function-calls function">terrain_point</span><span class="token punctuation">(</span>p <span class="token operator">+</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token decimal-float-literal number">0.1</span><span class="token punctuation">,</span> <span class="token decimal-float-literal number">0.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span> v<span class="token punctuation">;</span>
<span class="token keyword">let</span> tnz <span class="token operator">=</span> <span class="token function-calls function">terrain_point</span><span class="token punctuation">(</span>p <span class="token operator">+</span> <span class="token builtin">vec2</span><span class="token punctuation">&lt;</span><span class="token builtin">f32</span><span class="token punctuation">&gt;</span><span class="token punctuation">(</span><span class="token decimal-float-literal number">0.0</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token decimal-float-literal number">0.1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span> v<span class="token punctuation">;</span>
<span class="token keyword">let</span> pn <span class="token operator">=</span> <span class="token builtin">normalize</span><span class="token punctuation">(</span><span class="token builtin">cross</span><span class="token punctuation">(</span>tpz<span class="token punctuation">,</span> tpx<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> nn <span class="token operator">=</span> <span class="token builtin">normalize</span><span class="token punctuation">(</span><span class="token builtin">cross</span><span class="token punctuation">(</span>tnz<span class="token punctuation">,</span> tnx<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> n <span class="token operator">=</span> <span class="token punctuation">(</span>pn <span class="token operator">+</span> nn<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token decimal-float-literal number">0.5</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token function-calls function">Vertex</span><span class="token punctuation">(</span>v<span class="token punctuation">,</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>The <code>terrain_point</code> function takes an XZ point on the terrain and returns a <code>vec3</code> with the <code>y</code> value between the min and max height values.</p> <p><code>terrain_vertex</code> uses <code>terrain_point</code> to get it's position and also to compute of the normal of the surface by sampling 4 nearby points and uses them to compute the normal using some <a href="https://www.khanacademy.org/math/multivariable-calculus/thinking-about-multivariable-function/x786f2022:vectors-and-matrices/a/cross-products-mvc" target="_blank" rel="noopener noreferrer">cross products<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>.</p> <div class="note"><p>You'll notice that our <code>Vertex</code> struct doesn't include a texture coordinate. We could easily create texture coordinates by using the XZ coords of the vertices and having the texture sampler mirror the texture on the x and y axes, but heightmaps tend to have stretching when textured in this way.</p> <p>We'll cover a method called triplanar mapping to texture the terrain in a future tutorial. For now we'll just use a procedural texture that will create in the fragment shader we use to render the terrain.</p></div> <p>Now that we can get a vertex on the terrains surface we can fill our vertex and index buffers with actual data. We'll create a <code>gen_terrain()</code> function that will be the entry point for our compute shader:</p> <div class="language-wgsl extra-class"><pre class="language-wgsl"><code><span class="token punctuation">@</span><span class="token attributes attr-name">compute</span> <span class="token punctuation">@</span><span class="token attributes attr-name">workgroup_size</span><span class="token punctuation">(</span><span class="token int-literal number">64</span><span class="token punctuation">)</span>
<span class="token keyword">fn</span> <span class="token functions function">gen_terrain</span><span class="token punctuation">(</span>
<span class="token punctuation">@</span><span class="token builtin-attribute"><span class="token attribute attr-name">builtin</span><span class="token punctuation">(</span><span class="token built-in-values attr-value">global_invocation_id</span><span class="token punctuation">)</span></span> gid<span class="token punctuation">:</span> <span class="token builtin">vec3</span><span class="token punctuation">&lt;</span><span class="token builtin">u32</span><span class="token punctuation">&gt;</span>
<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// snipped...</span>
<span class="token punctuation">}</span>
</code></pre></div><p>We specify that <code>gen_terrain</code> is a compute shader entry point by annotating it with <code>stage(compute)</code>.</p> <p>The <code>workgroup_size()</code> is the number of workers that the GPU can allocate per <code>workgroup</code>. We specify the number of workers when we execute the compute shader. There are technically 3 parameters to this as work groups are a 3d grid, but if you don't specify them they default to 1. In other words <code>workgroup_size(64)</code> is equivalent to <code>workgroup_size(64, 1, 1)</code>.</p> <p>The <code>global_invocation_id</code> is a 3d index. This may seem weird, but you can think of work groups as a 3d grid of work groups. These workgroups have an internal grid of workers. The <code>global_invocation_id</code> is the id of the current worker relative to all the other works.</p> <p>Visually the workgroup grid would look something like this:</p> <p><img src="/learn-wgpu/assets/img/figure_work-groups.af52f221.jpg" alt="work group grid"></p> <div class="note"><p>It may be helpful to think of a compute shader as a function that is run in a bunch of nested for loops, but each loop is executed in parallel. It would look something like this:</p> <div class="language- extra-class"><pre class="language-text"><code>for wgx in num_workgroups.x:
for wgy in num_workgroups.y:
for wgz in num_workgroups.z:
var local_invocation_id = (wgx, wgy, wgz)
for x in workgroup_size.x:
for y in workgroup_size.x:
for z in workgroup_size.x:
var global_invocation_id = local_invocation_id * workgroup_size + (x, y, z);
gen_terrain(global_invocation_id)
</code></pre></div><p>If you want learn more about workgroups <a href="https://www.w3.org/TR/WGSL/#compute-shader-workgroups" target="_blank" rel="noopener noreferrer">check out the docs<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>.</p></div> <p>TODO:</p> <ul><li>Note changes to <code>create_render_pipeline</code></li> <li>Mention <code>swizzle</code> feature for cgmath</li> <li>Compare workgroups and workgroups sizes to nested for loops
<ul><li>Maybe make a diagram in blender?</li></ul></li> <li>Change to camera movement speed</li></ul></div> <footer class="page-edit"><!----> <div class="last-updated"><span class="prefix">Last Updated: </span> <span class="time">11/27/2023, 10:42:27 PM</span></div></footer> <!----> </main></div></div><div class="global-ui"><!----></div></div>
<script src="/learn-wgpu/assets/js/app.8ee4646c.js" defer></script><script src="/learn-wgpu/assets/js/2.2ef7287a.js" defer></script><script src="/learn-wgpu/assets/js/1.eaeeb819.js" defer></script><script src="/learn-wgpu/assets/js/31.fbafea68.js" defer></script>
</body>
</html>