Refraction Shader

DschoonmakerDschoonmaker Posts: 266Member

I'm making a water shader with refraction, but objects above water also get refracted. I've used the depth buffer to stop this before, but I can't get it to work. I already use the depth buffer for coloring:

float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
depth = depth * 2.0 - 1.0;
depth = PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);
depth += VERTEX.z;
depth = exp(-depth * beer_factor);
float colorDepth = clamp(1.0-depth,0.0,1.0);

The variable colorDepth is simply depth inverted and clamped between 0 and 1 to use for alpha.

The refraction in SpatialMaterial also has this problem, so that's no help either.

I thought refracting only if depth is less than zero would work, but that didn't do anything. Does anyone know how to do this?

Answers

  • DschoonmakerDschoonmaker Posts: 266Member

    Some unity shaders might help if they were adapted, but I haven't found anything yet.

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    I've collaborated with someone working on a water pack before. The project's abandoned, but the materials there can be useful, including this one.

  • DschoonmakerDschoonmaker Posts: 266Member

    I assume this is the code to refract only underwater objects?

    float depth_tex = texture(DEPTH_TEXTURE, SCREEN_UV + screen_offset).r;
                vec4 world_pos = INV_PROJECTION_MATRIX * vec4((SCREEN_UV + screen_offset) * 2.0 - 1.0, depth_tex * 2.0 - 1.0, 1.0);
                world_pos.xyz /= world_pos.w;
                float depth = distance(VERTEX.xyz, world_pos.xyz);
                if ((depth < fade_distance))
                {
            //code for refraction
    
  • SIsilicon28SIsilicon28 Posts: 757Moderator

    I think so.

  • DschoonmakerDschoonmaker Posts: 266Member

    So is world_pos the world position of the current pixel in the depth texture?

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    In this case, no. I named them wrong. That's actually the view space position.

  • DschoonmakerDschoonmaker Posts: 266Member

    How come world pos is a vec4? I thought just the depth relative to the water(using VERTEX.z, I think) needs to be compared. Unless I'm mistaken, all I need to do is get the depth relative to the water plane, and only refract if depth > 0.

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    The reason why it's a vec4 is because the w component was needed for perspective division. It's something that must be done after transforming the normalized device coordinates to view space and vice versa. Notice the part where I divided it by w.

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    And yeah, simply comparing the z components for depth would've sufficed too, but I believe it's more accurate if I compared it by distance.

  • DschoonmakerDschoonmaker Posts: 266Member

    So what is world_pos, how does getting the distance work if it's not actually the world position?

  • SIsilicon28SIsilicon28 Posts: 757Moderator
    edited May 7

    Like I said, I named it wrong. That's the position of the depth sample in view space. The vertex position of the water's surface is also in view space. Figuring out the distance between the two can tell you how much water is in the current fragment being rendered.

  • DschoonmakerDschoonmaker Posts: 266Member

    You said it's view space position? What does that mean?

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    @Dschoonmaker said:
    You said it's view space position? What does that mean?

    It means that the position of the fragment, or any arbitrary point really, is relative to the scene's the camera. The builtin VERTEX variable is in view space for example, meaning that it's position is relative to the camera.

  • DschoonmakerDschoonmaker Posts: 266Member

    So it could be compared to VERTEX.z to check if that pixel is underwater.

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    Correct.

  • DschoonmakerDschoonmaker Posts: 266Member

    Oops, I spent several minutes trying to figure out why your solution wasn't working. Then I put in an if/else statement for debugging(make the water red if it should refract, green if it shouldn't), but the water remained a clear blue. . .

    Then I realized I was typing in the shader for the bottom of the water. Godot has this weird little bug where if I click on the top of the water it selects the bottom & vice versa. Maybe it will work now :p

  • DschoonmakerDschoonmaker Posts: 266Member
    edited May 7

    Thanks for your help @SIsilicon28 , but neither of your solutions(there were two different methods in the code) worked. The second almost did, but some objects too far below the surface didn't refract. I'll try to find more about depth buffer & refraction.

  • SIsilicon28SIsilicon28 Posts: 757Moderator
    edited May 7

    Hmm, is your project in GLES2 or GLES3?
    Also the code you were referring to in the beginning only handles fogging, not refraction. That's in a different part of the shader.

  • DschoonmakerDschoonmaker Posts: 266Member

    It's GLES3.
    The code I referred to does only handle fogging, but should still work.

    void fragment(){
    //  vec2 uv = UV;
    //  uv.y += (0.01 * (sin(uv.x * 3.5 + (TIME*ripple_speed) * 0.35) + sin(uv.x * 4.8 + (TIME*ripple_speed) * 1.05) + sin(uv.x * 7.3 + (TIME*ripple_speed) * 0.45) / 3.0))*ripple_scale;
    //  uv.x += (0.12 * (sin(uv.y * 4.0 + (TIME*ripple_speed) * 0.5) + sin(uv.y * 6.8 + (TIME*ripple_speed) * 0.75) + sin(uv.y * 11.3 + (TIME*ripple_speed) * 0.2) / 3.0))*ripple_scale;
    //  uv.y += (0.12 * (sin(uv.x * 4.2 + (TIME*ripple_speed) * 0.64) + sin(uv.x * 6.3 + (TIME*ripple_speed) * 1.65) + sin(uv.x * 8.2 + (TIME*ripple_speed) * 0.45) / 3.0))*ripple_scale;
    //  NORMALMAP = texture(normalmap,uv*wave_scale).xyz;
        float ref_str = refraction/VERTEX.z;
        vec2 ref_ofs = (NORMALMAP.xy*ref_str)-(ref_str*0.5);
    //  ROUGHNESS = roughness;
    //  SPECULAR = specular;
    //  float foammask = texture(foam_texture,uv).r;
    //  vec4 foamcolor = mix(foamcolor_1*vec4(1,1,1,0),foamcolor_1,foammask);
    //  float foam2mask = texture(foam_texture,rotateUV(uv,vec2(0.5),90)).r;
    //  vec4 foam2 = foam2mask*foamcolor_2;
    //  vec4 foam = mix(foam2,foamcolor,foammask);
        float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
        depth = depth * 2.0 - 1.0;
        depth = PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);
        depth += VERTEX.z;
        depth = exp(-depth * beer_factor);
        float colorDepth = exp(-depth * beer_factor);
        colorDepth = clamp(1.0-depth,-1.0,1.0);
    //  ALBEDO = mix(color.rgb,foam.rgb,foam.a);
    //  TRANSMISSION = vec3(transmission);
        vec3 screenTexture;
        if(depth>0.0){
            depth = texture(DEPTH_TEXTURE, SCREEN_UV+ref_ofs).r;
            depth = depth * 2.0 - 1.0;
            depth = PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);
            depth += VERTEX.z;
            depth = exp(-depth * beer_factor);
            colorDepth = clamp(1.0-depth,0.0,1.0);
            screenTexture = textureLod(SCREEN_TEXTURE,SCREEN_UV+ref_ofs,murkiness).rgb;
        }
        else{
            screenTexture = texture(SCREEN_TEXTURE,SCREEN_UV).rgb;
        }
        ALPHA = clamp(mix(0.0,1.0,colorDepth+foammask+foam2mask),min_alpha,1.0);
        ALBEDO = mix(screenTexture,ALBEDO,ALPHA);
        ALPHA = 1.0;
    }
    

    This is my code, irrelevant parts are commented out. colorDepth is what I use for depth coloring, for now I leave depth the same(it could maybe be used).
    Note that

    if(depth>0.0){
    

    is a placeholder, I tried a lot of different solutions but reverted to this when they didn't work. I know it won't work, I just have it there for now.

    I think it should still work, especially because I've been declaring separate variables for the depth test(for debugging purposes).

  • DschoonmakerDschoonmaker Posts: 266Member

    If I use depth as albedo, it looks correct:

    Deep areas are white, and shallow areas are black. It would seem that if I only refract if depth is greater than zero, it should work. But it doesn't. Also, if I only refract if depth is greater than 0.5, I can see that deep pixels get refracted and shallow pixels don't. That's expected, but objects above the surface also get refracted. It behaves as if I took the absolute value of depth, but I don't, and I don't square it either. All I do is:

    float depthTest = texture(DEPTH_TEXTURE,SCREEN_UV).r;
    depthTest = depthTest * 2.0 - 1.0;
    depthTest = PROJECTION_MATRIX[3][2] / (depthTest + PROJECTION_MATRIX[2][2]);
    depthTest += VERTEX.z;
    if(depthTest>0.0){
        //refract
    
  • SIsilicon28SIsilicon28 Posts: 757Moderator

    In line 20 of the shader fragment you posted, it shows that you were applying beer's law twice on the depth value. Maybe that's the problem.

  • DschoonmakerDschoonmaker Posts: 266Member

    I noticed that also & removed it. That could be the problem, except depthTest is a new variable; everything I do to it is included in the code I posted(expect ALBEDO = vec3(depthTest), but that doesn't change it).

  • SIsilicon28SIsilicon28 Posts: 757Moderator

    Could you post your shader code again, so that I know what you have so far?

  • DschoonmakerDschoonmaker Posts: 266Member
    shader_type spatial;
    render_mode depth_draw_alpha_prepass,specular_schlick_ggx;
    
    uniform vec2 uv_scale = vec2(1);
    
    uniform vec4 color : hint_color;
    uniform float roughness : hint_range(0,1);
    
    uniform sampler2D normal1 : hint_normal;
    uniform sampler2D normal2 : hint_normal;
    uniform float normal_scale;
    
    uniform vec2 wave1_scroll;
    uniform vec2 wave2_scroll;
    
    uniform float beer_factor;
    uniform float min_alpha : hint_range(0,1);
    uniform float murkiness;
    uniform float refraction;
    
    uniform float specular_power;
    uniform float specular_strength;
    
    void vertex(){
        UV *= uv_scale;
    }
    
    void fragment(){
        vec3 wave1 = texture(normal1,UV+(wave1_scroll*TIME)-(wave1_scroll*0.5*TIME)).xyz*2.0-1.0;
        vec3 wave2 = texture(normal2,UV+(wave2_scroll*TIME)-(wave2_scroll*0.5*TIME)).xyz*2.0-1.0;
        NORMALMAP = normalize(vec3(wave1.xy+wave2.xy,wave1.z))*0.5+0.5;
        NORMALMAP_DEPTH = normal_scale;
        float fresnel = sqrt(1.0-dot(NORMAL,VIEW));
        float ref_str = refraction/VERTEX.z;
        vec2 ref_ofs = (NORMALMAP.xy*ref_str)-(ref_str*0.5);
        ROUGHNESS = roughness*(1.0-fresnel);
        float depth = texture(DEPTH_TEXTURE, SCREEN_UV).r;
        depth = depth * 2.0 - 1.0;
        depth = PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);
        depth += VERTEX.z;
        float colorDepth = exp(-depth * beer_factor);
        depth = exp(-depth * beer_factor);
        colorDepth = clamp(1.0-colorDepth,0.0,1.0);
        ref_str *= clamp(colorDepth,0,0.1);
        ref_ofs = (NORMALMAP.xy*ref_str)-(ref_str*0.5);
        ALBEDO = color.rgb;
        vec3 screenTexture;
        float depthTest = texture(DEPTH_TEXTURE,SCREEN_UV).r;
        depthTest = depthTest * 2.0 - 1.0;
        depthTest = PROJECTION_MATRIX[3][2] / (depthTest + PROJECTION_MATRIX[2][2]);
        if(depthTest>VERTEX.z){
            depth = texture(DEPTH_TEXTURE, SCREEN_UV+ref_ofs).r;
            depth = depth * 2.0 - 1.0;
            depth = PROJECTION_MATRIX[3][2] / (depth + PROJECTION_MATRIX[2][2]);
            depth += VERTEX.z;
            depth = exp(-depth * beer_factor);
            colorDepth = clamp(1.0-depth,0.0,1.0);
            screenTexture = textureLod(SCREEN_TEXTURE,SCREEN_UV+ref_ofs,colorDepth+murkiness).rgb;
        }
        else{
            screenTexture = textureLod(SCREEN_TEXTURE,SCREEN_UV,colorDepth+murkiness).rgb;
        }
        float alpha = clamp(colorDepth+min_alpha,min_alpha,1.0);
        ALBEDO = mix(screenTexture,ALBEDO,alpha);
    }   
    
    void light() {
        DIFFUSE_LIGHT += max(dot(NORMAL, LIGHT),0.0) * ATTENUATION * ALBEDO * LIGHT_COLOR;
        vec3 H = normalize(VIEW + LIGHT);
        float NdotH = max(0, dot(NORMAL, H));
        float specular = pow(NdotH, specular_power) * specular_strength * ATTENUATION.x;
        SPECULAR_LIGHT += specular*LIGHT_COLOR;
    }
    

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file