World space-based UV displacement in GLSL fragment shader

I need some help creating an rgb offset effect for images on my website. I've managed to get the basic functionality working, but I'm facing an issue where the channels are offset based on the uv of the texture. This means that if the images have different sizes, the offset doesn't look consistent across all images.

Below is the fragment shader code that I am currently using:

uniform sampler2D texture;
varying vec2 vUv; // vertex uv

void main() {
    vec2 uv = vUv;

    float red = texture2D(texture, vec2(uv.x, uv.y - .1)).r;
    float green = texture2D(texture, uv).g;
    float blue = texture2D(texture, vec2(uv.x, uv.y + .1)).b;
    float alpha = texture2D(texture, uv).a;

    gl_FragColor = vec4(vec3(red, green, blue), alpha);
}

Here's how it looks when rendered on the page:

I'm wondering if there's a way to normalize the uv offset without needing to pass in a uniform value. Any suggestions?

Answer №1

If you want to provide additional information like the offset amount, it would be a common practice.

uniform float offset1;
uniform float offset2;
uniform sampler2D texture;
varying vec2 vUv; // vertex uv

void main() {
    vec2 uv = vUv;

    float red = texture2D(texture, vec2(uv.x, uv.y + offset1)).r;
    float green = texture2D(texture, uv).g;
    float blue = texture2D(texture, vec2(uv.x, uv.y + offset2)).b;
    float alpha = texture2D(texture, uv).a;

    gl_FragColor = vec4(vec3(red, green, blue), alpha);
}

To adjust this in JavaScript, you can do something like:

  const uniforms = {
    offset1:  { value: 0 },
    offset2:  { value: 0 },
    ...
  };

...

  uniforms.offset1.value =  2 / textureHeight;
  uniforms.offset2.value = -2 / textureHeight;

A slight modification could look like this:

uniform vec2 channelOffsets[4];
uniform vec4 channelMult[4];
uniform sampler2D texture;
varying vec2 vUv; // vertex uv

void main() {
    vec2 uv = vUv;

    vec4 channel0 = texture2D(texture, uv + channelOffset[0]);
    vec4 channel1 = texture2D(texture, uv + channelOffset[1]);
    vec4 channel2 = texture2D(texture, uv + channelOffset[2]);
    vec4 channel3 = texture2D(texture, uv + channelOffset[3]);

    gl_FragColor = 
        channelMult[0] * channel0 +
        channelMult[1] * channel1 +
        channelMult[2] * channel2 +
        channelMult[3] * channel3 ; 
}

To set them:

  const uniforms = {
    channelOffsets:  { value: [
      new THREE.Vector2(),
      new THREE.Vector2(),
      new THREE.Vector2(),
      new THREE.Vector2(),
    ]},
    channelMults: { value: [
      new THREE.Vector4(1, 0, 0, 0),
      new THREE.Vector4(0, 1, 0, 0),
      new THREE.Vector4(0, 0, 1, 0),
      new THREE.Vector4(0, 0, 0, 1),
    ]},
    ....
  }

...

  uniforms.channelOffsets.value[0].y = -2 / textureHeight;
  uniforms.channelOffsets.value[2].y =  2 / textureHeight;

For a more flexible approach, using texture matrices instead of offsets and combining matrix transformations for more advanced effects is recommended.

Answer №2

To determine your position in screen coordinates (not 3D world space), you can utilize the gl_FragCoord function. For instance, on a 1080p monitor, gl_FragCoord.x will start at 0 on the left and increase to 1920 on the right side of the screen. The range for gl_FragCoord.y will be slightly less than [0, 1080] due to OS menu bars. To extract these coordinates in your fragment shader, you can do the following:

vec2 coord = gl_FragCoord.xy;

This method allows you to determine your pixel's exact location in screen-space without requiring another uniform input. For more information, refer to this link:

Answer №3

If you decide to utilize a WebGL 2.0 environment (refer to How to work with WebGL2), then you have the option to employ textureSize to obtain the size of the texture. For instance:

#version 300 es

uniform sampler2D textureSampler;
in      vec2      uvCoords;
out     vec4      resultColor;

void main() {
    vec2 uv = uvCoords;

    vec2  texSize   = vec2(textureSize(textureSampler, 0));
    float pixelSize = 10.0;
    float offset = pixelSize / texSize.y;

    float red   = texture(textureSampler, vec2(uv.x, uv.y - offset)).r;
    float green = texture(textureSampler, uv).g;
    float blue  = texture(textureSampler, vec2(uv.x, uv.y + offset)).b;
    float alpha = texture(textureSampler, uv).a;

    resultColor = vec4(red, green, blue, alpha);
}

Please remember to modify the name of the uniform for the texture sampler

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ways to determine the total number of npm packages installed, excluding development dependencies

Is there a specific NPM command I can run from the CLI to count only the installed NPM Modules in my package that are not Dev Dependencies? When I use npm ls, it lists all packages without distinguishing between regular dependencies and DevDependencies. ...

What is the most effective method for implementing a debounce effect on a field in React or React Native?

Currently, I am working on implementing a debounce effect in my useEffect. The issue is that the function inside it gets called as soon as there is a change in the input value. useEffect(() => { if (inputValue.length > 0) { fn(); ...

Javascript/Jquery - Eliminating line breaks when transferring text to a textarea by copying and pasting

Is there a method to paste text into a textarea that will automatically remove any line breaks or whitespaces? For instance, if I copy the following text and paste it into the textarea: abcdef, ghijkl, mnopqrs I would like it to appear in the textarea as ...

Attaching an event handler to $(document) within an Angular directive

One of the challenges I am facing is creating a directive that functions like a select box. When this select box is open and I click outside of it (anywhere else on the document), I want it to collapse. Although this JQuery code works within my directive, ...

Sending requests across domains from HTTPS to HTTP with callback functionality, and reversing the process

Prior to our conversation, I had created it using Flash. I uploaded a special file (crossdomain.xml) on the server side (https), while the Flash component was placed on the http server. Although they belong to different domains on the https and http serv ...

Oops! It seems that an invalid BCrypt hash triggered an unspecified "error" event that was not handled

Attempting to develop an API for login in nodejs. However, when checking the login route via HTTP requester, nothing is displayed in the output. Instead, the command line shows an error Error: Uncaught, unspecified "error" event. (Not a valid BCrypt hash.) ...

Not all records are compatible with PHP Echo when placed within a paragraph

Looking for some assistance with a simple form set up; <div class="formholder"> <form action="column1.php" method="post"> <input type="text" placeholder="URL:" name="url" required=""><br> <input type="t ...

KnockoutJS is not recognizing containerless if binding functionality

I was recently faced with the task of displaying a specific property only if it is defined. If the property is not defined, I needed to show a DIV element containing some instructions. Despite my efforts using $root and the bind property, I couldn't ...

removing functionality across various inputs

I have a JavaScript function that deletes the last digit in an input field. It works fine with one input, but not with another. It only erases the digit in the first input. <script> function deleteDigit(){ var inputString=docu ...

What is the reason the child component is not being displayed?

The code within the APP.js component looks like this: import React from "react"; import Exam from "./exam.js"; export default function App() { return ( <Exam> <h1>hashemi</h1> </Exam> ); } Similarly, the ...

Fluidly adjust text field and label fields in forms

I came across a code snippet that allows for dynamically adding form text fields. However, I needed to ensure that each text field has a corresponding label which increments by one every time a new field is added. While I have successfully added the labels ...

How can JQuery be used to implement "scrolling" buttons and automatically update the main page with fresh data?

I am attempting to achieve the following: 1 - Use jQuery to update index.html with the content from output.html (only update when there are differences in data, and ideally only update the parts that have changed). 2 - Add two buttons in the header ...

Determine the size of an SVG while taking into consideration any strokes applied

Is there a way to seamlessly position an SVG in the corner of a div, despite having a dynamically generated stroke? Calculating the distance to the outermost part of the border proves difficult when dealing with irregular shapes like stars. Here's my ...

Manipulate state in parent component from child component using onClick function with React hooks

Hello, I am facing a challenge trying to store data from a modal (child function) within the main (parent) function. Depending on which button is clicked, the modal loads specific HTML content (all buttons perform the same action). export default function ...

Updating a particular element within nested arrays: best practices

I have developed a data table containing student records using material UI. Each row represents a different student array with a fee verification button. My goal is to change the fee status when the button is clicked for a specific student, but currently t ...

When attempting to seed, the system could not locate any metadata for the specified "entity"

While working on a seeding system using Faker with TypeORM, I encountered an error during seeding: ...

Exploring the power of AngularJS factory promises with the use of Pouch

After creating a service to retrieve data from a local json file, I proceeded to integrate this service with my controllers. However, I recently discovered how to fetch the same data from a pouchDB database. $scope.healthSystems = HealthSystemData.get(); ...

Tips for preventing the sidebar from extending beyond the footer

I am facing an issue with my sidebar that follows as I scroll. How can I make it stop following when it reaches the footer? Here's what I have attempted so far: $(function() { var floatPosition = parseInt($("#left_menu").css('top')); ...

Perform a JSON POST request from an HTML script to a Node.JS application hosted on a different domain

In an attempt to send string data via a post json request using JavaScript within an .erb.html file, I am facing the challenge of sending it to a node.js app on another domain that uses express to handle incoming requests. After researching online, I have ...

Learn the steps for generating an array of objects in AngularJS or JavaScript

I have an array named $scope.data2 and I am looking to create another array of arrays based on the data provided below: $scope.data2 = [ {"dt":"07 Jul 2015","avgdelay":"10","code_sent_time":"07 Jul 2015 12:30 PM" ...