Skip to content
This repository was archived by the owner on Jan 26, 2023. It is now read-only.

Real/currents #28

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open

Conversation

dancingfrog
Copy link

@dancingfrog dancingfrog commented Apr 8, 2020

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 Real-Currents@f3b0a1c#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 a NAME definition into the vertex and fragment shaders which becomes a property on the material object (available in the callback as material.vertName and material.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:

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 heightMap;

void main() {
	float displacement = texture(heightMap, uv).r;

	vec3 displace_along_normal = vec3(normal * displacement);

	vec3 displaced_position = position + (0.99 * displace_along_normal);

//	vec4 pos = vec4(position, 1.0);
	vec4 pos = vec4(displaced_position, 1.0);
	vec4 model_view_pos = VIEW * MODEL * pos;

	v_normal = (MODEL_INVERSE_TRANSPOSE * vec4(normal, 0.0)).xyz;

	#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.svelte:

<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 terrain;
    const terrainMap = new Image();
    const heightMap = new Image();
    terrainMap.alt = 'Terrain Texture';
    heightMap.crossOrigin = terrainMap.crossOrigin = '';

    let webgl;
    let displacementTexture = null;
    let process_extra_shader_components = (gl, material, model) => {
        // console.log("Process Extra Shader Components");
        const program = material.program;

        if (material.vertName === "terrain-vert") {
            // console.log(material.vertName);

            if (!!displacementTexture) {
                const displacementTextureLocation = gl.getUniformLocation(program, "heightMap");

                gl.activeTexture(gl.TEXTURE1);
                gl.bindTexture(gl.TEXTURE_2D, displacementTexture);
                gl.uniform1i(displacementTextureLocation, 1);


            }

        }

    };

    onMount(() => {
        let frame;

        console.log(webgl);

        if (!!displacementTexture === false) {
            // Create a texture and create initial bind
            displacementTexture = webgl.createTexture();
            webgl.bindTexture(webgl.TEXTURE_2D, displacementTexture);
            webgl.bindTexture(webgl.TEXTURE_2D, null);
        }

        // Texture constants
        const level = 0;
        const internalFormat = webgl.RGBA;
        const format = webgl.RGBA;
        const type = webgl.UNSIGNED_BYTE;

        heightMap.addEventListener('load', function () {
            // Now that the image has loaded copy it to the texture.
            console.log("Bind to texture");

            webgl.bindTexture(webgl.TEXTURE_2D, displacementTexture);
            webgl.texImage2D(webgl.TEXTURE_2D, level, internalFormat, format, type, heightMap);
            webgl.generateMipmap(webgl.TEXTURE_2D);
            webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.NEAREST_MIPMAP_LINEAR);
            webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.NEAREST_MIPMAP_LINEAR);
        });

        heightMap.src = "images/heightmap.png";

        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={process_extra_shader_components}>
    <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: 0xffffff, 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, h)}
                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>

heightmap.png:
heightmap.png

And this is what you see:
demo

@dancingfrog dancingfrog marked this pull request as ready for review April 8, 2020 15:59
@dancingfrog
Copy link
Author

dancingfrog commented Apr 10, 2020

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)

@dancingfrog
Copy link
Author

... 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):

image

geometry/box.mjs Outdated
Comment on lines 15 to 24
[ (obj.x + obj.w), (obj.y + obj.h), (obj.z + obj.d) ], // 0
[ obj.x, (obj.y + obj.h), (obj.z + obj.d) ], // 1
[ (obj.x + obj.w), obj.y, (obj.z + obj.d) ], // 2
[ obj.x, obj.y, (obj.z + obj.d) ], // 3
[ obj.x, (obj.y + obj.h), obj.z ], // 4
[ (obj.x + obj.w), (obj.y + obj.h), obj.z ], // 5
[ obj.x, obj.y, obj.z ], // 6
[ (obj.x + obj.w), obj.y, obj.z ] // 7
];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WTF is this chaotic formating?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like some tabs snuck in there... I can fix that, but is that the extent of your code review? Any other thoughts?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants