0 votes

Im trying to make a "simple" shader that tiles and rotates a texture atlas ramdomly to avoid visual repetition, it works fine when used in a normal material, but when I try to implement it on a world triplanar I can't get the uv's to rotate on their own position instead of the world origin so the overall texture gets a little skewed.

vec2 rotateUV(vec2 uv, vec2 pivot, float rotation)
{
    rotation = radians(rotation);

    float cosa = cos(rotation);
    float sina = sin(rotation);
    uv -= pivot;
    return vec2(
        cosa * uv.x - sina * uv.y,
        cosa * uv.y + sina * uv.x 
        ) + pivot;
}

vec2 rand_tiled_uv(vec2 uv)
{
    vec2 seed = floor(uv);

    vec2 r_uv;
    if (rotate)
    {
        r_uv = rotateUV( uv, vec2(.5), rotation_d * floor( rand_range(seed + rng, 0,4) ));
    }
    else
    {
        r_uv = uv;
    }

    lowp float x = rand_range(seed + rng, 0, tiles);
    lowp float y = rand_range(seed + rng + 1., 0, tiles);
    vec2 rand_offset = vec2(1.) - ( vec2(1. / tiles) * ceil(vec2(x,y)) );


    return (r_uv ) + rand_offset;
}

vec4 triplanar_texture(sampler2D p_sampler,vec3 p_weights,vec3 p_triplanar_pos) 
{
    vec4 samp=vec4(0.0);
    samp+= texture( p_sampler, rand_tiled_uv(p_triplanar_pos.xy) ) * p_weights.z;
    samp+= texture( p_sampler, rand_tiled_uv(p_triplanar_pos.xz) ) * p_weights.y;
    samp+= texture( p_sampler, rand_tiled_uv(p_triplanar_pos.zy) * vec2(-1.0,1.0) ) * p_weights.x;

    return samp;
}

(the rest of the shader is just a normal converted spatial material)

normal texture
normal texture

randomly rotated by 90 (some lines are bigger or misaligned)

randomly rotated by 95 (the crosses aren't centered)

rotated normally by 100

This is what I want:

I googled and tried all similar questions on here (I think I should use this somehow) and even on the unity forums but I just distort the texture beyond recognition. Any help?

Godot version 3.2.3
in Engine by (98 points)
edited by

2 Answers

0 votes
Best answer

Got a solution from Reddit by u/kleonc

You gotta do a matrix rotation (it works in 3D):

shader_type canvas_item;

uniform float uvRotationDegrees = 0.0;
uniform vec2 uvRotationPivot = vec2(0.5);

mat2 get2dRotationMatrix(float angleRadians)
{
    float s = sin(angleRadians);
    float c = cos(angleRadians);
    return mat2(vec2(c, s), vec2(-s, c));
}

void fragment()
{
    vec2 uv = UV; // whatever your UV is
    uv -= floor(uv); // make it into [0.0, 1.0) x [0.0, 1.0) range
    uv -= uvRotationPivot; // move origin to the rotation pivot
    uv *= get2dRotationMatrix(radians(uvRotationDegrees)); // rotate
    uv += uvRotationPivot; // move origin back

    COLOR = texture(TEXTURE, uv); // use calculated uv
}
by (98 points)

To be sure it worked don't forget to add more than one Mesh using same shader material(not copy same godot resource) to the scene. And test on sphere.

0 votes

Are you trying to do this https://cyangamedev.wordpress.com/2020/01/28/worldspace-uvs-triplanar-mapping/ in godot?

While we usually use the ones stored in the mesh UV channels it can
also be useful to use the World space Position of the fragment/pixel
instead, especially if there aren’t any UV coordinates available.

You have mesh UVs use it.
With 99.9% probability your shader will not be portable between GLES2, GLES3, Vulcan backend if you use this method.

Edit:
Lazy way to have tri-planar mapping in shader https://godotengine.org/qa/28820/tri-planar-mapping-in-godot-3

by (846 points)
edited by

No, I already have triplanar working, I used the one that godot already has (see my last picture), but I want the rotation to not be triplanar, only the position and scale. I want each "tile" of the texture to rotate individualy. I think I have to find the current "tile" position and offset the world origin to it, but I don't know how.

It's hard to show on pictures but if you use the rotateUV function you can see that everything rotates around a single point in the world

I don't think I can use the mesh uv's, you use the world as uv in triplanar

Here some nice Reddit users calculated fragment world coordinates in godot.https://www.reddit.com/r/godot/comments/8cgyi3/how_to_get_fragment_world_coordinates_in_canvas/

Let's hope they are right I'm not doing 3d in godot.

Already tried that one a bunch of times, doesn't work

Let's take one step back. Why do you want to use World tri-planar (one shared UV for all meshes using same shader material. UV coordinates is just world x,y,z coordinates) instead of local tri-planar (just generates UV for mesh without UV information. You may need to normalize UVs in some engines. I do not know about godot.). You will not need to search for individual tiles in generated global UV with local tri-planar.

Cause it's for a random tile shader, I want to use it on large terrains (heightmaps) or meshes (CGS) that wont have proper UV to break repetition

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.