mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
101.7k stars 35.3k forks source link

Example of WGSL shader for Three.js WebGPU renderer #27299

Closed andreasrosdal closed 8 months ago

andreasrosdal commented 9 months ago

Description

Hello! I am searching for examples of a WGSL shader for the Three.js WebGPU renderer.

I am going to port this WebGL GLSL shader to WebGPU WGSL: https://github.com/fciv-net/fciv-net/blob/main/freeciv-web/src/main/webapp/javascript/webgl/shaders/terrain_fragment_shader.glsl

Solution

Some examples of WGSL shaders with various typical use cases.

Alternatives

I did try asking on the Three.js forum, but found no solution.

Additional context

This is part of the effort to port https://Fciv.net to WebGPU here: https://github.com/fciv-net/fciv-net

Mugen87 commented 9 months ago

Before you write native WGSL code, have you considered to write TSL instead?

@sunag developed an example that helps with converting GLSL to TSL. Check out: webgpu_tsl_transpiler

The great thing is that with TSL your custom materials work with both WebGPU and WebGL which is in some sense mandatory for apps who want to target a wide audience.

There is also webgpu_tsl_editor which let's you inspect the native code that is transpiled from TSL.

andreasrosdal commented 9 months ago

Yes, I can use TSL. Is there some documentation or examples of using TSL? How do I initialize and run the TSL?

Mugen87 commented 9 months ago

You can search for tslFn in the examples. There are some examples using it but the best entry point is probably webgpu_materials. It shows how tslFn is used to setup two simple desaturate nodes. A real documentation is still missing though.

andreasrosdal commented 9 months ago

When submitting this shader https://github.com/fciv-net/fciv-net/blob/main/freeciv-web/src/main/webapp/javascript/webgl/shaders/terrain_fragment_shader.glsl to https://threejs.org/examples/webgpu_tsl_transpiler I get this error message: image

Error: tokens[(tonumeric i)] is undefined

So it would be useful with a more specific error message.

Next I will try to simplify the shader that I try to transpile.

Should I paste in the fragment shader or vertex shader into the transpiler?

Mugen87 commented 9 months ago

It shouldn't matter whether the GLSL code runs in the vertex or fragment shader.

I guess the transpiler wasn't tested with such a large piece of code yet.

sunag commented 9 months ago

There are some tokens like preprocessor that have not been added support, the transpiler should not understand the syntax and give a low level bug. The Transpiler is currently function oriented, but we will certainly improve it to the point of transpiling the vertex+fragment shader completely.

andreasrosdal commented 9 months ago

I managed to transpile to TSL. Thank you!

Some comments:

Input: https://github.com/fciv-net/fciv-net/blob/main/freeciv-web/src/main/webapp/javascript/webgl/shaders/terrain_fragment_shader.glsl

Results:

// Three.js Transpiler r159

import { varying, uniform, float, vec3, vec2, vec4, int, floor, sub, If, round, dot, sin, fract, mul, cond, mod, mix, smoothstep, div, clamp, normalize, max, add, tslFn } from 'three/nodes';

ifdef;
endif;
varying;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
uniform;
varying;
varying;
varying;
uniform;
const terrain_inaccessible = float( 0.0 ).toVar();
const terrain_lake = float( 10.0 ).toVar();
const terrain_coast = float( 20.0 ).toVar();
const terrain_floor = float( 30.0 ).toVar();
const terrain_arctic = float( 40.0 ).toVar();
const terrain_desert = float( 50.0 ).toVar();
const terrain_forest = float( 60.0 ).toVar();
const terrain_grassland = float( 70.0 ).toVar();
const terrain_hills = float( 80.0 ).toVar();
const terrain_jungle = float( 90.0 ).toVar();
const terrain_mountains = float( 100.0 ).toVar();
const terrain_plains = float( 110.0 ).toVar();
const terrain_swamp = float( 120.0 ).toVar();
const terrain_tundra = float( 130.0 ).toVar();
const is_river_modifier = float( 10.0 / 255.0 ).toVar();
const roadtype_1 = float( 1.0 ).toVar();
const roadtype_2 = float( 2.0 ).toVar();
const roadtype_3 = float( 3.0 ).toVar();
const roadtype_4 = float( 4.0 ).toVar();
const roadtype_5 = float( 5.0 ).toVar();
const roadtype_6 = float( 6.0 ).toVar();
const roadtype_7 = float( 7.0 ).toVar();
const roadtype_8 = float( 8.0 ).toVar();
const roadtype_9 = float( 9.0 ).toVar();
const roadtype_all = float( 42.0 ).toVar();
const roadtype_10 = float( 10.0 ).toVar();
const roadtype_11 = float( 11.0 ).toVar();
const roadtype_12 = float( 12.0 ).toVar();
const roadtype_13 = float( 13.0 ).toVar();
const roadtype_14 = float( 14.0 ).toVar();
const roadtype_15 = float( 15.0 ).toVar();
const roadtype_16 = float( 16.0 ).toVar();
const roadtype_17 = float( 17.0 ).toVar();
const roadtype_18 = float( 18.0 ).toVar();
const roadtype_19 = float( 19.0 ).toVar();
const railtype_all = float( 43.0 ).toVar();
const beach_high = float( 50.9 ).toVar();
const beach_blend_high = float( 50.4 ).toVar();
const beach_blend_low = float( 49.8 ).toVar();
const beach_low = float( 48.0 ).toVar();
const blend_amount = float( 0.0 ).toVar();
const mountains_low_begin = float( 74.0 ).toVar();
const mountains_low_end = float( 74.5 ).toVar();
const mountains_high = float( 75.10 ).toVar();
const ambiant = vec3( 0.27, 0.55, 1. ).toVar();
const light = vec3( 0.8, 0.6, 0.7 ).toVar();
const texture_coord = vec2().toVar();
const dx = float( 0.0 ).toVar();
const dy = float( 0.0 ).toVar();
const mdx = float( 0.0 ).toVar();
const mdy = float( 0.0 ).toVar();
const terrain_here = float().toVar();
const road_here_r = float().toVar();
const road_here_g = float().toVar();
const road_here_b = float().toVar();
const sprite_pos0_x = float( 0.0 ).toVar();
const sprite_pos0_y = float( 0.75 ).toVar();
const sprite_pos1_x = float( 0.25 ).toVar();
const sprite_pos1_y = float( 0.75 ).toVar();
const sprite_pos2_x = float( 0.5 ).toVar();
const sprite_pos2_y = float( 0.75 ).toVar();
const sprite_pos3_x = float( 0.75 ).toVar();
const sprite_pos3_y = float( 0.75 ).toVar();
const sprite_pos4_x = float( 0.0 ).toVar();
const sprite_pos4_y = float( 0.5 ).toVar();
const sprite_pos5_x = float( 0.25 ).toVar();
const sprite_pos5_y = float( 0.5 ).toVar();
const sprite_pos6_x = float( 0.5 ).toVar();
const sprite_pos6_y = float( 0.5 ).toVar();
const sprite_pos7_x = float( 0.75 ).toVar();
const sprite_pos7_y = float( 0.5 ).toVar();
const sprite_pos8_x = float( 0.0 ).toVar();
const sprite_pos8_y = float( 0.25 ).toVar();
const sprite_pos9_x = float( 0.25 ).toVar();
const sprite_pos9_y = float( 0.25 ).toVar();
const sprite_pos10_x = float( 0.5 ).toVar();
const sprite_pos10_y = float( 0.25 ).toVar();
const sprite_pos11_x = float( 0.75 ).toVar();
const sprite_pos11_y = float( 0.25 ).toVar();
const sprite_pos12_x = float( 0.0 ).toVar();
const sprite_pos12_y = float( 0.0 ).toVar();
const sprite_pos13_x = float( 0.25 ).toVar();
const sprite_pos13_y = float( 0.0 ).toVar();
const sprite_pos14_x = float( 0.5 ).toVar();
const sprite_pos14_y = float( 0.0 ).toVar();
const sprite_pos15_x = float( 0.75 ).toVar();
const sprite_pos15_y = float( 0.0 ).toVar();
const border_e = vec4().toVar();
const border_w = vec4().toVar();
const border_n = vec4().toVar();
const border_s = vec4().toVar();

const main = tslFn( () => {

    If( vColor.r.equal( 0.0 ), () => {

        gl_FragColor.rgb.assign( vec3( int( 0 ), int( 0 ), int( 0 ) ) );

        If( mouse_x.greaterThanEqual( int( 0 ) ).and( mouse_y.greaterThanEqual( int( 0 ) ).and( mouse_x.equal( int( floor( map_x_size.mul( vUv.x ) ) ) ).and( mouse_y.equal( int( floor( map_y_size.mul( sub( 1.0, vUv.y ) ) ) ) ) ) ) ), () => {

            gl_FragColor.rgb.assign( vec3( 0.3, 0.3, 0.3 ) ); 

        } );

        return; 

    } );

    const rnd = float( fract( sin( dot( vec2( round( vUv.x.mul( 10000.0 ) ).div( 10000.0 ), round( vUv.y.mul( 10000.0 ) ).div( 10000.0 ) ), vec2( 12.98, 78.233 ) ) ).mul( 43758.5453 ) ) ).toVar();
    const terrain_type = vec4( texture2D( maptiles, vec2( vUv.x.add( rnd.sub( 0.5 ).div( mul( 8.0, map_x_size ) ) ), vUv.y.add( rnd.sub( 0.5 ).div( mul( 8.0, map_y_size ) ) ) ) ) ).toVar();
    const border_color = vec4( cond( borders_visible, texture2D( borders, vec2( vUv.x, vUv.y ) ), vec4( int( 0 ), int( 0 ), int( 0 ), int( 0 ) ) ) ).toVar();
    const road_type = vec4( texture2D( roadsmap, vec2( vUv.x, vUv.y ) ) ).toVar();
    const c = vec3().toVar();
    const terrain_color = vec4().toVar();

    If( terrain_type.g.equal( is_river_modifier ), () => {

        beach_high.assign( 50.45 );
        beach_blend_high.assign( 50.20 ); 

    } );

    dx.assign( mod( map_x_size.mul( vUv.x ), 1.0 ) );
    dy.assign( mod( map_y_size.mul( vUv.y ), 1.0 ) );
    mdx.assign( map_x_size.mul( vUv.x ).div( 4.0 ).sub( mul( 0.25, floor( map_x_size.mul( vUv.x ) ) ) ) );
    mdy.assign( map_y_size.mul( vUv.y ).div( 4.0 ).sub( mul( 0.25, floor( map_y_size.mul( vUv.y ) ) ) ) );
    terrain_here.assign( floor( terrain_type.r.mul( 256.0 ) ) );

    If( terrain_here.equal( terrain_grassland ), () => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( grassland, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } ); 

    } ).elseif( ( terrain_here.equal( terrain_plains ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( plains, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_lake ) ) => {

        If( vPosition.y.lessThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( plains, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_coast ) ) => {

        If( vPosition.y.lessThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

            If( fract( vPosition.x.add( 502.0 ).div( 35.71 ) ).lessThan( 0.018 ).or( fract( vPosition.z.add( 2.0 ).div( 35.71 ) ).lessThan( 0.018 ) ), () => {

                terrain_color.rgb.assign( terrain_color.rgb.mul( 1.45 ) ); 

            } ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( plains, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_floor ) ) => {

        If( vPosition.y.lessThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( ocean, texture_coord ) );

            If( fract( vPosition.x.add( 502.0 ).div( 35.71 ) ).lessThan( 0.018 ).or( fract( vPosition.z.add( 2.0 ).div( 35.71 ) ).lessThan( 0.018 ) ), () => {

                terrain_color.rgb.assign( terrain_color.rgb.mul( 1.7 ) ); 

            } ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( plains, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_arctic ) ) => {

        texture_coord.assign( vec2( dx, dy ) );
        terrain_color.assign( texture2D( arctic, texture_coord ) );

    } ).elseif( ( terrain_here.equal( terrain_desert ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( desert, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_forest ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( grassland, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_hills ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( hills, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_jungle ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( plains, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_mountains ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( mountains, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_swamp ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( swamp, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).elseif( ( terrain_here.equal( terrain_tundra ) ) => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( tundra, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } ).else( () => {

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( plains, texture_coord ) ); 

        } ).else( () => {

            texture_coord.assign( vec2( dx, dy ) );
            terrain_color.assign( texture2D( coast, texture_coord ) );

        } );

    } );

    c.assign( terrain_color.rgb );

    If( vPosition.y.greaterThan( mountains_high ), () => {

        blend_amount.assign( sub( 3.0, mountains_high.sub( vPosition.y ) ).div( 3.0 ).sub( 1.0 ) );
        const Ca = vec4( texture2D( arctic, vec2( dx, dy ) ) ).toVar();
        const Cb = vec4( texture2D( mountains, vec2( dx, dy ) ) ).toVar();
        c.assign( mix( Ca.rgb, Cb.rgb, sub( 1.0, blend_amount ) ) ); 

    } ).elseif( ( vPosition.y.greaterThan( mountains_low_begin ) ) => {

        If( vPosition.y.lessThan( mountains_low_end ), () => {

            const Cmountain = vec4( texture2D( mountains, vec2( dx, dy ) ) ).toVar();
            c.assign( mix( terrain_color.rgb, Cmountain.rgb, smoothstep( mountains_low_begin, mountains_low_end, vPosition.y ) ) ); 

        } ).else( () => {

            const Cb = vec4( texture2D( mountains, vec2( dx, dy ) ) ).toVar();
            c.assign( Cb.rgb );

        } );

    } );

    If( fract( vPosition.x.add( 502.0 ).div( 35.71 ) ).lessThan( 0.018 ).or( fract( vPosition.z.add( 2.0 ).div( 35.71 ) ).lessThan( 0.018 ) ), () => {

        c.assign( c.sub( 0.085 ) ); 

    } );

    If( vPosition.y.lessThan( beach_high ).and( vPosition.y.greaterThan( beach_low ).and( terrain_here.!=( terrain_arctic ) ) ), () => {

        texture_coord.assign( vec2( dx, dy ) );

        If( vPosition.y.greaterThan( beach_blend_high ), () => {

            blend_amount.assign( beach_high.sub( beach_blend_high ).sub( beach_high.sub( vPosition.y ) ).div( beach_high.sub( beach_blend_high ) ) );
            const Cbeach = vec4( texture2D( desert, texture_coord ).mul( 1.4 ) ).toVar();
            c.assign( mix( terrain_color.rgb, Cbeach.rgb, sub( 1.0, blend_amount ) ) ); 

        } ).elseif( ( terrain_type.g.equal( is_river_modifier ) ) => {

            const Cbeach = vec4( texture2D( coast, texture_coord ) ).toVar();
            c.assign( Cbeach.rgb.mul( 2.4 ) );

        } ).elseif( ( vPosition.y.lessThan( beach_blend_low ) ) => {

            blend_amount.assign( beach_blend_low.sub( vPosition.y ).div( 2.0 ) );
            const Cbeach = vec4( texture2D( coast, texture_coord ).mul( 3.5 ) ).toVar();
            c.assign( mix( terrain_color.rgb, Cbeach.rgb, sub( 1.0, blend_amount ) ) );

        } ).else( () => {

            const Cbeach = vec4( texture2D( desert, texture_coord ) ).toVar();
            c.assign( Cbeach.rgb.mul( 1.38 ) );

        } ); 

    } );

    If( vColor.g.greaterThan( 0.3 ).and( vColor.g.lessThan( 0.7 ) ), () => {

        texture_coord.assign( vec2( dx, dy ) );
        const t1 = vec4( texture2D( irrigation, texture_coord ) ).toVar();
        c.assign( mix( c, vec3( t1 ), t1.a ) ); 

    } );

    If( vColor.g.greaterThan( 0.9 ), () => {

        texture_coord.assign( vec2( dx, dy ) );
        const t1 = vec4( texture2D( farmland, texture_coord ) ).toVar();
        c.assign( mix( c, vec3( t1 ), t1.a ) ); 

    } );

    road_here_r.assign( floor( road_type.r.mul( 256.0 ) ) );
    road_here_g.assign( floor( road_type.g.mul( 256.0 ) ) );
    road_here_b.assign( floor( road_type.b.mul( 256.0 ) ) );

    If( road_here_r.equal( 0.0 ).or( vPosition.y.lessThan( beach_blend_low ) ), () => {

    } ).elseif( ( road_here_r.equal( roadtype_1 ).and( road_here_g.equal( 0.0 ).and( road_here_b.equal( 0.0 ) ) ) ) => {

        texture_coord.assign( vec2( mdx.add( sprite_pos0_x ), mdy.add( sprite_pos0_y ) ) );
        const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
        c.assign( mix( c, vec3( t1 ), t1.a ) );

    } ).elseif( ( road_here_r.equal( roadtype_all ) ) => {

        texture_coord.assign( vec2( mdx.add( sprite_pos1_x ), mdy.add( sprite_pos1_y ) ) );
        const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
        c.assign( mix( c, vec3( t1 ), t1.a ) );
        texture_coord.assign( vec2( mdx.add( sprite_pos3_x ), mdy.add( sprite_pos3_y ) ) );
        t1.assign( texture2D( roadsprites, texture_coord ) );
        c.assign( mix( c, vec3( t1 ), t1.a ) );
        texture_coord.assign( vec2( mdx.add( sprite_pos5_x ), mdy.add( sprite_pos5_y ) ) );
        t1.assign( texture2D( roadsprites, texture_coord ) );
        c.assign( mix( c, vec3( t1 ), t1.a ) );
        texture_coord.assign( vec2( mdx.add( sprite_pos7_x ), mdy.add( sprite_pos7_y ) ) );
        t1.assign( texture2D( roadsprites, texture_coord ) );
        c.assign( mix( c, vec3( t1 ), t1.a ) );

    } ).elseif( ( road_here_r.equal( railtype_all ) ) => {

        texture_coord.assign( vec2( mdx.add( sprite_pos1_x ), mdy.add( sprite_pos1_y ) ) );
        const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
        c.assign( mix( c, vec3( t1 ), t1.a ) );
        texture_coord.assign( vec2( mdx.add( sprite_pos3_x ), mdy.add( sprite_pos3_y ) ) );
        t1.assign( texture2D( railroadsprites, texture_coord ) );
        c.assign( mix( c, vec3( t1 ), t1.a ) );
        texture_coord.assign( vec2( mdx.add( sprite_pos5_x ), mdy.add( sprite_pos5_y ) ) );
        t1.assign( texture2D( railroadsprites, texture_coord ) );
        c.assign( mix( c, vec3( t1 ), t1.a ) );
        texture_coord.assign( vec2( mdx.add( sprite_pos7_x ), mdy.add( sprite_pos7_y ) ) );
        t1.assign( texture2D( railroadsprites, texture_coord ) );
        c.assign( mix( c, vec3( t1 ), t1.a ) );

    } ).elseif( ( road_here_r.greaterThan( 0.0 ).and( road_here_r.lessThan( roadtype_10 ) ) ) => {

        If( road_here_r.equal( roadtype_2 ).or( road_here_g.equal( roadtype_2 ).or( road_here_b.equal( roadtype_2 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos1_x ), mdy.add( sprite_pos1_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_3 ).or( road_here_g.equal( roadtype_3 ).or( road_here_b.equal( roadtype_3 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos2_x ), mdy.add( sprite_pos2_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_4 ).or( road_here_g.equal( roadtype_4 ).or( road_here_b.equal( roadtype_4 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos3_x ), mdy.add( sprite_pos3_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_5 ).or( road_here_g.equal( roadtype_5 ).or( road_here_b.equal( roadtype_5 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos4_x ), mdy.add( sprite_pos4_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_6 ).or( road_here_g.equal( roadtype_6 ).or( road_here_b.equal( roadtype_6 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos5_x ), mdy.add( sprite_pos5_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_7 ).or( road_here_g.equal( roadtype_7 ).or( road_here_b.equal( roadtype_7 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos6_x ), mdy.add( sprite_pos6_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_8 ).or( road_here_g.equal( roadtype_8 ).or( road_here_b.equal( roadtype_8 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos7_x ), mdy.add( sprite_pos7_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_9 ).or( road_here_g.equal( roadtype_9 ).or( road_here_b.equal( roadtype_9 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos8_x ), mdy.add( sprite_pos8_y ) ) );
            const t1 = vec4( texture2D( roadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

    } ).elseif( ( road_here_r.greaterThanEqual( roadtype_10 ).and( road_here_r.lessThan( roadtype_all ) ) ) => {

        If( road_here_r.equal( roadtype_10 ).and( road_here_g.equal( 0.0 ).and( road_here_b.equal( 0.0 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos0_x ), mdy.add( sprite_pos0_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_12 ).or( road_here_g.equal( roadtype_12 ).or( road_here_b.equal( roadtype_12 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos1_x ), mdy.add( sprite_pos1_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_13 ).or( road_here_g.equal( roadtype_13 ).or( road_here_b.equal( roadtype_13 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos2_x ), mdy.add( sprite_pos2_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_14 ).or( road_here_g.equal( roadtype_14 ).or( road_here_b.equal( roadtype_14 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos3_x ), mdy.add( sprite_pos3_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_15 ).or( road_here_g.equal( roadtype_15 ).or( road_here_b.equal( roadtype_15 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos4_x ), mdy.add( sprite_pos4_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_16 ).or( road_here_g.equal( roadtype_16 ).or( road_here_b.equal( roadtype_16 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos5_x ), mdy.add( sprite_pos5_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_17 ).or( road_here_g.equal( roadtype_17 ).or( road_here_b.equal( roadtype_17 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos6_x ), mdy.add( sprite_pos6_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_18 ).or( road_here_g.equal( roadtype_18 ).or( road_here_b.equal( roadtype_18 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos7_x ), mdy.add( sprite_pos7_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

        If( road_here_r.equal( roadtype_19 ).or( road_here_g.equal( roadtype_19 ).or( road_here_b.equal( roadtype_19 ) ) ), () => {

            texture_coord.assign( vec2( mdx.add( sprite_pos8_x ), mdy.add( sprite_pos8_y ) ) );
            const t1 = vec4( texture2D( railroadsprites, texture_coord ) ).toVar();
            c.assign( mix( c, vec3( t1 ), t1.a ) ); 

        } );

    } );

    If( borders_visible.and( border_color.r.greaterThan( 0.546875 ).and( border_color.r.lessThan( 0.5625 ).and( border_color.b.equal( 0.0 ).and( border_color.g.equal( 0.0 ) ) ) ).not() ), () => {

        border_e.assign( texture2D( borders, vec2( vUv.x.add( div( 0.06, map_x_size ) ), vUv.y ) ) );
        border_w.assign( texture2D( borders, vec2( vUv.x.sub( div( 0.06, map_x_size ) ), vUv.y ) ) );
        border_n.assign( texture2D( borders, vec2( vUv.x, vUv.y.add( div( 0.06, map_x_size ) ) ) ) );
        border_s.assign( texture2D( borders, vec2( vUv.x, vUv.y.sub( div( 0.06, map_x_size ) ) ) ) );

        If( border_n.r.!=( border_color.r ).or( border_n.g.!=( border_color.g ).or( border_n.b.!=( border_color.b ).or( border_s.r.!=( border_color.r ).or( border_s.g.!=( border_color.g ).or( border_n.b.!=( border_color.b ).or( border_e.r.!=( border_color.r ).or( border_e.g.!=( border_color.g ).or( border_e.b.!=( border_color.b ).or( border_w.r.!=( border_color.r ).or( border_w.g.!=( border_color.g ).or( border_w.b.!=( border_color.b ) ) ) ) ) ) ) ) ) ) ) ), () => {

            c.assign( border_color.rbg ); 

        } ).else( () => {

            c.assign( mix( c, border_color.rbg, 0.10 ) );

        } ); 

    } );

    const x = float( sub( 1.0, clamp( vPosition.y.sub( 38. ).div( 15. ), 0., 1. ) ) ).toVar();
    const Cb = vec4( texture2D( coast, vec2( dx, dy ) ).mul( 0.5 ) ).toVar();
    c.assign( mix( c, Cb.rgb, x ) );
    const shade_factor = float( add( 0.32, mul( 1.42, max( 0., dot( vNormal, normalize( light ) ) ) ) ) ).toVar();

    If( mouse_x.greaterThanEqual( int( 0 ) ).and( mouse_y.greaterThanEqual( int( 0 ) ).and( mouse_x.equal( int( floor( map_x_size.mul( vUv.x ) ) ) ).and( mouse_y.equal( int( floor( map_y_size.mul( sub( 1.0, vUv.y ) ) ) ) ) ) ) ), () => {

        shade_factor.addAssign( +0.6 ); 

    } );

    If( selected_x.greaterThanEqual( int( 0 ) ).and( selected_y.greaterThanEqual( int( 0 ) ).and( selected_x.equal( int( floor( map_x_size.mul( vUv.x ) ) ) ).and( selected_y.equal( int( floor( map_y_size.mul( sub( 1.0, vUv.y ) ) ) ) ) ) ) ), () => {

        shade_factor.addAssign( +1.0 ); 

    } );

    c.assign( c.mul( vColor.r ) );
    gl_FragColor.rgb.assign( mix( c.mul( shade_factor ), ambiant, vPosition_camera.z.sub( 550. ).mul( 0.0000187 ) ) );

} );

// layouts

main.setLayout( {
    name: 'main',
    type: 'void',
    inputs: []
} );

export { main };
sunag commented 9 months ago

The code transpiled in the editor was a project to be placed in a javascript and used as a Node, this would be equivalent to a function. The result for the end user should be close to:

import { terrain } from './Terrain.js';

const material = new Nodes.MeshBasicNodeMaterial();
material.colorNode = terrain( { ...parameters } );

This will allow connections like:

material.colorNode = terrain( { ...parameters } ).add( otherMethod() ).mul( someValue );

For example, this example would not need to use a vertex pass because we have Nodes to facilitate this stage.

import { attribute, normalLocal, uniform } from 'three/nodes';

const vertColor = attribute( 'vertColor', 'vec3' );
const vNormal = normalLocal;
const map_x_size = uniform( 0 );
...

A node call for this example would be similar to this:

material.colorNode = terrain( {
    vertColor: attribute( 'vertColor', 'vec3' )
    vNormal: normalLocal,
    map_x_size: uniform( 0 ),
    ...
} );

Currently the Transpiler was designed to handle functions, so the conversion, although it was done, had problems. The next advances in Transpiler will be in this direction.

andreasrosdal commented 9 months ago

Currently the Transpiler was designed to handle functions, so the conversion, although it was done, had problems. The next advances in Transpiler will be in this direction.

Which problems does the conversion have? Are you able to manually identity and fix them?

Thank you for the assistance with this WebGPU port. I hope that Freeciv3D can be a realistic use-case for migrating to WebGPU in Three.js and that we can cooperate in this WebGPU migration.

Would it be possible to create a minimal working transpiled TSL WebGPU html and js page for this shader?

Spiri0 commented 8 months ago

@andreasrosdal I could probably help you with that if you'd like. I can show you in the forum how the node system works so that you can get your shader wgsl capable. Then this issue can be closed here.

andreasrosdal commented 8 months ago

Yes, I would appreciate help porting the shader to TS WebGPU. I tried asking in the forum. It's too complex.

Also the current shader doesn't work in Mac m2 so there is some platform support to improve.

Spiri0 commented 8 months ago

@andreasrosdal I wrote to you in the three.js forum. The point here can then be closed by the developers.