2
votes

I have a GLSL shader that reads from one of the channels (e.g. R) of an input texture and then writes to the same channel in an output texture. This channel has to be selected by the user.

What I can think of right now is to just use an int uniform and tons of if-statements:

uniform sampler2D uTexture;
uniform int uChannelId;
varying vec2 vUv;

void main() {

    //read in data from texture
    vec4 t = texture2D(uTexture, vUv);
    float data;
    if (uChannelId == 0) {
        data = t.r;
    } else if (uChannelId == 1) {
        data = t.g;
    } else if (uChannelId == 2) {
        data = t.b;
    } else {
        data = t.a;
    }

    //process the data...
    float result = data * 2;  //for example

    //write out
    if (uChannelId == 0) {
        gl_FragColor = vec4(result, t.g, t.b, t.a);
    } else if (uChannelId == 1) {
        gl_FragColor = vec4(t.r, result, t.b, t.a);
    } else if (uChannelId == 2) {
        gl_FragColor = vec4(t.r, t.g, result, t.a);
    } else {
        gl_FragColor = vec4(t.r, t.g, t.b, result);
    }

}

Is there any way of doing something like a dictionary access such as t[uChannelId]?

Or perhaps I should have 4 different versions of the same shader, each of which processes a different channel, so that I can avoid all the if-statements?

What is the best way to do this?

EDIT: To be more specific, I am using WebGL (Three.js)

2

2 Answers

4
votes

There is such a way, and it is as simple as you actually wrote it in the question. Just use t[channelId]. To quote the GLSL Spec (This is from Version 3.30, Section 5.5, but applies to other versions as well):

Array subscripting syntax can also be applied to vectors to provide numeric indexing. So in

vec4 pos;

pos[2] refers to the third element of pos and is equivalent to pos.z. This allows variable indexing into a vector, as well as a generic way of accessing components. Any integer expression can be used as the subscript. The first component is at index zero. Reading from or writing to a vector using a constant integral expression with a value that is negative or greater than or equal to the size of the vector is illegal. When indexing with non-constant expressions, behavior is undefined if the index is negative, or greater than or equal to the size of the vector.

Note that for the first part of your code, you use this to access a specific channel of a texture. You could also use the ARB_texture_swizzle functionality. In that case, you would just use a fxied channel, say r, for access in the shader and what swizzle the actual texture channels so that wahtever channel you want to access becomes r.

Update: as the target platform turned out to be webgl, these suggestions are not available. However, a simple solution would be to use a vec4 uniform in place of uChannelID which is 1.0 for the selected component and 0.0 for all others. Say this variable is called uChannelSel. You could use data=dot(t, uChannelSel) in the first part and gl_FragColor=(vec4(1.0)-uChannelSel) * t + uChannelSel*result for the second part.

1
votes

as i'm sure you know, branching can be expensive in shaders. however, it sounds like it'll always be the same channel in a pass (yes?), so you might maintain enough cohesion to see good performance.

it's been a good while since i've used GLSL, but if you're using a newer version, maybe you could do some bitwise shifting (<< or >>) magic? you would read the texture into int instead of vec4, then shift it a number of bits depending on which channel you want to read.