Open dancingfrog opened 4 years ago
Actually on further exploration of the capabilities of @sveltejs/gl I have found that all I needed to do to achieve the scene above was to include the bumpmap
texture uniform in the vertex shader (no custom uniforms or render-time hooks necessary)...
terrain-vert.glsl
:
in vec3 position;
in vec3 normal;
out vec3 v_normal;
#if defined(has_colormap) || defined(has_specularitymap) || defined(has_normalmap) || defined(has_bumpmap)
#define has_textures true
#endif
#ifdef has_textures
in vec2 uv;
out vec2 v_uv;
#endif
#if defined(has_normalmap) || defined(has_bumpmap)
out vec3 v_view_position;
#endif
out vec3 v_surface_to_light[NUM_LIGHTS];
#ifdef has_specularity
out vec3 v_surface_to_view[NUM_LIGHTS];
#endif
#ifdef USE_FOG
out float v_fog_depth;
#endif
// So, standard @sveltejs/gl default shader so far, and then ...
#define NAME terrain-vert
// texture containing elevation data
uniform sampler2D bumpmap;
void main() {
float displacement = texture(bumpmap, uv).r;
vec3 displace_along_normal = vec3(normal * displacement);
vec3 displaced_position = position + (0.99 * displace_along_normal);
v_normal = (MODEL_INVERSE_TRANSPOSE * vec4(normal, 0.0)).xyz;
vec4 pos = vec4(displaced_position, 1.0);
vec4 model_view_pos = VIEW * MODEL * pos;
#ifdef has_textures
v_uv = uv;
#endif
#if defined(has_normalmap) || defined(has_bumpmap)
v_view_position = model_view_pos.xyz;
#endif
#ifdef USE_FOG
v_fog_depth = -model_view_pos.z;
#endif
for (int i = 0; i < NUM_LIGHTS; i += 1) {
PointLight light = POINT_LIGHTS[i];
vec3 surface_world_position = (MODEL * pos).xyz;
v_surface_to_light[i] = light.location - surface_world_position;
#ifdef has_specularity
v_surface_to_view[i] = CAMERA_WORLD_POSITION - surface_world_position;
#endif
}
gl_Position = PROJECTION * model_view_pos;
}
app.velte
:
<script>
import { onMount } from 'svelte';
import * as GL from '@sveltejs/gl';
import terrainVert from './shaders/custom/terrain-vert.glsl';
export let title;
export let color = '#F7C77B';
let w = 1;
let h = 1;
let d = 1;
const light = {};
function adjustColor (clr, height = 1) {
const r = parseInt('0x' + clr.substr(1, 2), 16),
g = parseInt('0x' + clr.substr(3, 2), 16),
b = parseInt('0x' + clr.substr(5, 2), 16);
const hr = Math.floor(r * (height / 0.25)),
hb = Math.floor(b * (height / 0.25));
return Math.abs((((hr < 255) ? hr : r) << 16) + (g << 8) + ((hb < 255) ? hb : b));
}
let webgl;
let terrain;
onMount(() => {
let frame;
terrain = new GL.Texture("/images/heightmap.png", { width: 512, height: 512 });
const loop = () => {
frame = requestAnimationFrame(loop);
light.x = 3 * Math.sin(Date.now() * 0.001);
light.y = 2.5 + 2 * Math.sin(Date.now() * 0.0004);
light.z = 3 * Math.cos(Date.now() * 0.002);
};
loop();
return () => cancelAnimationFrame(frame);
});
</script>
<GL.Scene bind:gl={webgl} backgroundOpacity=1.0 process_extra_shader_components={null}>
<GL.Target id="center" location={[0, h/2, 0]}/>
<GL.OrbitControls maxPolarAngle={Math.PI / 2} let:location>
<GL.PerspectiveCamera {location} lookAt="center" near={0.01} far={1000}/>
</GL.OrbitControls>
<GL.AmbientLight intensity={0.3}/>
<GL.DirectionalLight direction={[-1,-1,-1]} intensity={0.5}/>
<!-- ground -->
<GL.Mesh
geometry={GL.terrain()}
location={[0, -0.01, 0]}
rotation={[-90, 0, 0]}
scale={h}
vert={terrainVert}
uniforms={{ color: adjustColor(color, h), alpha: 1.0, bumpmap: terrain }}
/>
<GL.Mesh
geometry={GL.plane()}
location={[0, h/2 - 0.05, 0]}
rotation={[-90, 0, 0]}
scale={h}
uniforms={{ color: 0x0066ff, alpha: 0.45 }}
transparent
/>
<!-- moving light -->
<GL.Group location={[light.x,light.y,light.z]}>
<GL.Mesh
geometry={GL.sphere({ turns: 36, bands: 36 })}
location={[0,0.2,0]}
scale={0.1}
uniforms={{ color: adjustColor(color, h), emissive: adjustColor(color) }}
/>
<GL.PointLight
location={[0,0,0]}
color={adjustColor(color, 1.0)}
intensity={0.6}
/>
</GL.Group>
</GL.Scene>
<div class="controls">
<label>
<input type="color" style="height: 64px" bind:value={color}>
</label>
<label>
<input type="range" bind:value={h} min={0.5} max={2} step={0.1}><br />
size ({h})
</label>
</div>
The hook could still be useful for binding other inputs to custom shaders, but I guess many effects are possible with what's currently in @sveltejs/gl (although I did have to use the GL.terrain mesh, or else the vertex displacement would not be possible)
... for instance, accessing the normal map texture in the vertex shader and computing a directional lighting model (which doesn't require a very fine resolution):
Hi @Rich-Harris,
This is a collection of commits I've made to the WebGL extension over the past couple of weeks. I'm not expecting you to accept this pull request whole-sale, but hoping you have some time to look through the different updates and see if any of them are worth incorporating into future versions of @sveltejs/gl, particularly https://github.com/Real-Currents/SvelteGL/commit/f3b0a1c9459af2266630542948c47e53e40da876#diff-32fcd5a8151257542efe4e1860086ad5
This inserts a hook (as a callback that is passed to
<GL.Scene>
) that allows custom routines to run during each draw cycle. This is a very powerful capability in combination with dropping aNAME
definition into the vertex and fragment shaders which becomes a property on the material object (available in the callback asmaterial.vertName
andmaterial.fragName
respectively). The shaders were updated to version 3.00 while I was in there.I also played around with a adding some new mesh types, including a planar
<GL.terrain>
that is basically the plane type, with a bunch more vertices in the mesh. Using the technique mentioned above I can now implement a displacement map in a custom vertex shader, like so...EDIT: slight improvement to
terrain-vert.glsl
:app.svelte
:heightmap.png
:And this is what you see: