I'm in GLSL texture hell: I load 4 different textures of the same size in uniform sampler2D variables in my fragment shader and try to access them with different texture coordinates:
uniform sampler2D image0, image1, image2, image3;
varying highp vec2 texCoord;
void main()
{
highp vec2 tc = vec2(texCoord.x, mod(1.0-texCoord.y, 0.2) + 0.2);
lowp vec4 color0 = texture2D(image0, tc);
lowp vec4 color1 = texture2D(image1, tc);
lowp vec4 color2 = texture2D(image2, tc);
lowp vec4 color3 = texture2D(image3, tc);
if (texCoord.y < 0.2) { gl_FragColor = color0; }
else if (texCoord.y < 0.4) { gl_FragColor = color1; }
else if (texCoord.y < 0.6) { gl_FragColor = color2; }
else if (texCoord.y < 0.8) { gl_FragColor = color3; }
else { gl_FragColor = vec4(0.0); }
}
The texCoord comes from the vertex shader of course:
uniform lowp float ratio;
attribute highp vec4 vertex;
varying highp vec2 texCoord;
void main()
{
gl_Position = vertex;
texCoord.x = ((vertex.x * 0.5) + 0.5) * ratio;
texCoord.y = (vertex.y * 0.5) + 0.5;
}
I get 5 separate slices, from image0, image2 (!! not image1), image3, image3 (again!), and black (this would hold a merge of the various texture, not important in this context, my problem being getting the right textures first). I checked the image loading code multiple times, I do load 4 different images:
- (void)linkTexture:(GLenum)tex image:(Image *)image varName:(const char *)varName
{
GLint texLocation;
texLocation = glGetUniformLocation(program, varName);
glUniform1i(texLocation, tex-GL_TEXTURE0);
glActiveTexture(tex);
glBindTexture(GL_TEXTURE_2D, image->texID);
}
and further down:
loadTexture("new_york_0.jpg", &image0, &renderer);
[self linkTexture:GL_TEXTURE0 image:&image0 varName:"image0"];
loadTexture("new_york_1.jpg", &image1, &renderer);
[self linkTexture:GL_TEXTURE1 image:&image1 varName:"image1"];
loadTexture("new_york_2.jpg", &image2, &renderer);
[self linkTexture:GL_TEXTURE2 image:&image2 varName:"image2"];
loadTexture("new_york_3.jpg", &image3, &renderer);
[self linkTexture:GL_TEXTURE3 image:&image3 varName:"image3"];
There must be something very wrong in the way I expect the GPU to lookup textures but I have no clue what it is.
Can someone shine a light?
Well in the first case of course you get 5 slices from the same texture.
Think about what you are doing.
Take a y coordinate of 0.6.
(1 - 0.6) = 0.4
mod( 0.4, 0.2 ) = 0.0
0.0 + 0.2 = 0.2
Basically you force the y coordinate to be in the range 0.2 to 0.4 which will always be image1. I'm not sure why you are seeing image2 though ...
As for the 2nd case ... this heavily implies that the texture coord you are receiving is wrong. So .. do you want to show us the vertex shader as well as this fragment shader?
I have to admit, though, I can't understand why you don't just put the 5 slices into a single texture and just render it ...
You may want to check out this:
http://www.vis.uni-stuttgart.de/glsldevil/index.html#downloads
glUniform1i modifies the uniform values for the current program. Make sure you are calling glUseProgram(myProgram) before your calls to linkTexture.
Related
I'm drawing a 2d plan on the screen using webgl. I would like to rotate the plan a bit to give a 3d impression.
current:
wanted:
My first approach was to use vanishing points like drawing in perspective but I didn't know how to change the y coordinate and I didn't get to the end. Is there an easier way to rotate the output?
Here is my code:
uniform float scale;
uniform vec2 ratio;
uniform vec2 center;
in vec3 fillColor;
in vec2 position;
out vec3 color;
void main() {
color = fillColor;
gl_Position = vec4((position - center) * ratio, 0.0, scale);
}
If you want to build a whole game engine or a complex animation, you will need to dig into perspective projection matrices.
But if you just want to achieve this little effect and try to understand how it works, you can just use the w coord of gl_Position. This coordinate is essential to tell the GPU how to interpolate UV textures in a valid 3D way, for example. And it will be divided to x, y and z.
So let's assume you want to display a rectangle. You will need two triangles.
4 vertices will suffice if you use TRIANGLE_STRIP mode. We could use only one attribute, but for the sake of tutorial, I will use two:
Vertex #
attPos
attUV
0
-1, +1
0, 0
1
-1, +1
0, 1
2
+1, +1
1, 0
3
+1, -1
1, 1
And all the logic will be in the vertex shader:
uniform float uniScale;
uniform float uniAspectRatio;
attribute vec2 attPos;
attribute vec2 attUV;
varying vec2 varUV;
void main() {
varUV = attUV;
gl_Position = vec4(
attPos.x * uniScale,
attPos.y * uniAspectRatio,
1.0,
attUV.y < 0.5 ? uniScale : 1.0
);
}
The line attUV.y < 0.5 ? uniScale : 1.0 means
If attUV.y is 0, then use uniScale
Otherwise use 1.0
The attUV attribute let's you use a texture if you want. In this example,
I just simulate a checkboard with this fragment shader:
precision mediump float;
const float MARGIN = 0.1;
const float CELLS = 8.0;
const vec3 ORANGE = vec3(1.0, 0.5, 0.0);
const vec3 BLUE = vec3(0.0, 0.6, 1.0);
varying vec2 varUV;
void main() {
float u = fract(varUV.x * CELLS);
float v = fract(varUV.y * CELLS);
if (u > MARGIN && v > MARGIN) gl_FragColor = vec4(BLUE, 1.0);
else gl_FragColor = vec4(ORANGE, 1.0);
}
You can see all this in action in this CopePen:
https://codepen.io/tolokoban/full/oNpBRyO
I am a complete noob when it comes to creating shaders. Or better said, I just learned about it yesterday.
I am trying to create a really simple circle. I thouht I finally figured it out but it turns out to be to large. It should match the DisplayObject size where the filter is applied to.
The fragment shader:
precision mediump float;
varying vec2 vTextureCoord;
vec2 resolution = vec2(1.0, 1.0);
void main() {
vec2 uv = vTextureCoord.xy / resolution.xy;
uv -= 0.5;
uv.x *= resolution.x / resolution.y;
float r = 0.5;
float d = length(uv);
float c = smoothstep(d,d+0.003,r);
gl_FragColor = vec4(vec3(c,0.5,0.0),1.0);
}
Example using Pixi.js:
var app = new PIXI.Application();
document.body.appendChild(app.view);
var background = PIXI.Sprite.fromImage("required/assets/bkg-grass.jpg");
background.width = 200;
background.height = 200;
app.stage.addChild(background);
var vertexShader = `
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
varying vec2 vTextureCoord;
void main(void)
{
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}
`;
var fragShader = `
precision mediump float;
varying vec2 vTextureCoord;
vec2 resolution = vec2(1.0, 1.0);
void main() {
vec2 uv = vTextureCoord.xy / resolution.xy;
uv -= 0.5;
uv.x *= resolution.x / resolution.y;
float r = 0.5;
float d = length(uv);
float c = smoothstep(d,d+0.003,r);
gl_FragColor = vec4(vec3(c,0.5,0.),1.0);
}
`;
var filter = new PIXI.Filter(vertexShader, fragShader);
filter.padding = 0;
background.filters = [filter];
body { margin: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.2/pixi.js"></script>
Pixi.js's vTextureCoord do not go from 0 to 1.
From the docs
V4 filters differ from V3. You can't just add in the shader and assume that texture coordinates are in the [0,1] range.
...
Note: vTextureCoord multiplied by filterArea.xy is the real size of bounding box.
If you want to get the pixel coordinates, use uniform filterArea, it will be passed to the filter automatically.
uniform vec4 filterArea;
...
vec2 pixelCoord = vTextureCoord * filterArea.xy;
They are in pixels. That won't work if we want something like "fill the ellipse into a bounding box". So, lets pass dimensions too! PIXI doesnt do it automatically, we need a manual fix:
filter.apply = function(filterManager, input, output)
{
this.uniforms.dimensions[0] = input.sourceFrame.width
this.uniforms.dimensions[1] = input.sourceFrame.height
// draw the filter...
filterManager.applyFilter(this, input, output);
}
Lets combine it in shader!
uniform vec4 filterArea;
uniform vec2 dimensions;
...
vec2 pixelCoord = vTextureCoord * filterArea.xy;
vec2 normalizedCoord = pixelCoord / dimensions;
Here's your snippet updated.
var app = new PIXI.Application();
document.body.appendChild(app.view);
var background = PIXI.Sprite.fromImage("required/assets/bkg-grass.jpg");
background.width = 200;
background.height = 200;
app.stage.addChild(background);
var vertexShader = `
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
varying vec2 vTextureCoord;
void main(void)
{
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aTextureCoord;
}
`;
var fragShader = `
precision mediump float;
varying vec2 vTextureCoord;
uniform vec2 dimensions;
uniform vec4 filterArea;
void main() {
vec2 pixelCoord = vTextureCoord * filterArea.xy;
vec2 uv = pixelCoord / dimensions;
uv -= 0.5;
float r = 0.5;
float d = length(uv);
float c = smoothstep(d,d+0.003,r);
gl_FragColor = vec4(vec3(c,0.5,0.),1.0);
}
`;
var filter = new PIXI.Filter(vertexShader, fragShader);
filter.apply = function(filterManager, input, output)
{
this.uniforms.dimensions[0] = input.sourceFrame.width
this.uniforms.dimensions[1] = input.sourceFrame.height
// draw the filter...
filterManager.applyFilter(this, input, output);
}
filter.padding = 0;
background.filters = [filter];
body { margin: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.2/pixi.js"></script>
It seems you've stumbled upon weird floating point precision problems: texture coordinates (vTextureCoord) in your fragment shader aren't strictly in (0, 1) range. Here's what I've got when I've added line gl_FragColor = vec4(vTextureCoord, 0, 1):
It seems good, but if we inspect it closely, lower right pixel should be (1, 1, 0), but it isn't:
The problem goes away if instead of setting size to 500 by 500 we use power-of-two size (say, 512 by 512), the problem goes away:
The other possible way to mitigate the problem would be to try to circumvent Pixi's code that computes projection matrix and provide your own that transforms smaller quad into desired screen position.
I'm displaying a grid of particle clouds using shaders. Every time a user clicks a cloud, that cloud disappears and a new one takes its place. The curious thing is that the memory usage in the GPU climbs every time a new cloud replaces an old one - regardless of whether that new cloud is larger or smaller (and the buffer sizes always stay the same - the unused points are simply displayed offscreen with no color). After less than 10 clicks the GPU maxes out and crashes.
Here is my physics shader where the new positions are updated - I pass in the new position values for the new cloud by updating certain values in the the tOffsets texture. After that are my two (vert and frag) visual effects shaders. Can you see my efficiency issue? Or could this be a garbage collection matter? - Thanks in advance!
Physics Shader (frag only):
// Physics shader: This shader handles the calculations to move the various points. The position values are rendered out to at texture that is passed to the next pair of shaders that add the sprites and opacity.
// the tPositions sampler is added to this shader by Three.js's GPUCompute script
uniform sampler2D tOffsets;
uniform sampler2D tGridPositionsAndSeeds;
uniform sampler2D tSelectionFactors;
uniform float uPerMotifBufferDimension;
uniform float uTime;
uniform float uXOffW;
...noise functions omitted for brevity...
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 offsets = texture2D( tOffsets, uv ).xyzw;
float alphaMass = offsets.z;
float cellIndex = offsets.w;
if (cellIndex >= 0.0) { // this point will be rendered on screen
float damping = 0.98;
float texelSize = 1.0 / uPerMotifBufferDimension;
vec2 perMotifUV = vec2( mod(cellIndex, uPerMotifBufferDimension)*texelSize, floor(cellIndex / uPerMotifBufferDimension)*texelSize );
perMotifUV += vec2(0.5*texelSize);
vec4 selectionFactors = texture2D( tSelectionFactors, perMotifUV ).xyzw;
float swapState = selectionFactors.x;
vec4 gridPosition = texture2D( tGridPositionsAndSeeds, perMotifUV ).xyzw;
vec2 noiseSeed = gridPosition.zw;
vec4 nowPos;
vec2 velocity;
nowPos = texture2D( tPositions, uv ).xyzw;
velocity = vec2(nowPos.z, nowPos.w);
if ( swapState == 0.0 ) { // if no new position values are ready to be swapped in for this point
nowPos = texture2D( tPositions, uv ).xyzw;
velocity = vec2(nowPos.z, nowPos.w);
} else { // if swapState == 1, this means new position values are ready to be swapped in for this point
nowPos = vec4( -(uTime) + offsets.x, offsets.y, 0.0, 0.0 );
velocity = vec2(0.0, 0.0);
}
...physics calculations omitted for brevity...
vec2 newPosition = vec2(nowPos.x - velocity.x, nowPos.y - velocity.y);
// Write new position out to a texture for processing in the visual effects shader
gl_FragColor = vec4(newPosition.x, newPosition.y, velocity.x, velocity.y);
} else { // this point will not be rendered on screen
// Write new position out off screen (all -1 cellIndexes have off-screen offset values)
gl_FragColor = vec4( offsets.x, offsets.y, 0.0, 0.0);
}
From the physics shader the tPositions texture with the points' new movements is rendered out and passed to the visual effects shaders:
Visual Effects Shader (vert):
uniform sampler2D tPositions; // passed in from the Physics Shader
uniform sampler2D tSelectionFactors;
uniform float uPerMotifBufferDimension;
uniform sampler2D uTextureSheet;
uniform float uPointSize;
uniform float uTextureCoordSizeX;
uniform float uTextureCoordSizeY;
attribute float aTextureIndex;
attribute float aAlpha;
attribute float aCellIndex;
varying float vCellIndex;
varying vec2 vTextureCoords;
varying vec2 vTextureSize;
varying float vAlpha;
varying vec3 vColor;
...omitted noise functions for brevity...
void main() {
vec4 tmpPos = texture2D( tPositions, position.xy );
vec2 pos = tmpPos.xy;
vec2 vel = tmpPos.zw;
vCellIndex = aCellIndex;
if (vCellIndex >= 0.0) { // this point will be rendered onscreen
float texelSize = 1.0 / uPerMotifBufferDimension;
vec2 perMotifUV = vec2( mod(aCellIndex, uPerMotifBufferDimension)*texelSize, floor(aCellIndex / uPerMotifBufferDimension)*texelSize );
perMotifUV += vec2(0.5*texelSize);
vec4 selectionFactors = texture2D( tSelectionFactors, perMotifUV ).xyzw;
float aSelectedMotif = selectionFactors.x;
float aColor = selectionFactors.y;
float fadeFactor = selectionFactors.z;
vTextureCoords = vec2( aTextureIndex * uTextureCoordSizeX, 0 );
vTextureSize = vec2( uTextureCoordSizeX, uTextureCoordSizeY );
vAlpha = aAlpha * fadeFactor;
vColor = vec3( 1.0, aColor, 1.0 );
gl_PointSize = uPointSize;
} else { // this point will not be rendered onscreen
vAlpha = 0.0;
vColor = vec3(0.0, 0.0, 0.0);
gl_PointSize = 0.0;
}
gl_Position = projectionMatrix * modelViewMatrix * vec4( pos.x, pos.y, position.z, 1.0 );
}
Visual Effects Shader (frag):
uniform sampler2D tPositions;
uniform sampler2D uTextureSheet;
varying float vCellIndex;
varying vec2 vTextureCoords;
varying vec2 vTextureSize;
varying float vAlpha;
varying vec3 vColor;
void main() {
gl_FragColor = vec4( vColor, vAlpha );
if (vCellIndex >= 0.0) { // this point will be rendered onscreen, so add the texture
vec2 realTexCoord = vTextureCoords + ( gl_PointCoord * vTextureSize );
gl_FragColor = gl_FragColor * texture2D( uTextureSheet, realTexCoord );
}
}
Thanks to #Blindman67's comment above, I sorted out the problem. It had nothing to do with the shaders. In the Javascript (Three.js) I needed to signal the GPU to delete old textures before adding the updated ones.
Everytime I update a texture (most of mine are DataTextures) I need to call dispose() on the existing texture before creating and updating the new one, like so:
var textureHandle; // holds a reference to the current texture uniform value
textureHandle.dispose(); // ** deallocates GPU memory **
textureHandle = new THREE.DataTexture( textureData, dimension, dimension, THREE.RGBAFormat, THREE.FloatType );
textureHandle.needsUpdate = true;
uniforms.textureHandle.value = textureHandle;
I'm trying to learn how to make shaders, and a little while ago, I posted a question here : GLSL Shader - Shadow between 2 textures on a plane
So, the answer gave me the right direction to take, but I have some trouble for checking if there is a fragment that is not transparent between the current fragment and the light position.
So here is the code :
Vertex Shader :
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
varying vec2 uvVarying;
varying vec3 normalVarying;
varying vec3 posVarying;
uniform vec4 uvBounds0;
uniform mat4 agk_World;
uniform mat4 agk_ViewProj;
uniform mat3 agk_WorldNormal;
void main()
{
vec4 pos = agk_World * vec4(position,1);
gl_Position = agk_ViewProj * pos;
vec3 norm = agk_WorldNormal * normal;
posVarying = pos.xyz;
normalVarying = norm;
uvVarying = uv * uvBounds0.xy + uvBounds0.zw;
}
And the fragment shader :
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
uniform sampler2D texture0;
uniform sampler2D texture1;
varying vec2 uvVarying;
varying vec3 normalVarying;
varying vec3 posVarying;
uniform vec4 uvBounds0;
uniform vec2 playerPos;
uniform vec2 agk_resolution;
uniform vec4 agk_PLightPos;
uniform vec4 agk_PLightColor;
uniform vec4 agk_ObjColor;
void main (void)
{
vec4 lightPos = agk_PLightPos;
lightPos.x = playerPos.x;
lightPos.y = -playerPos.y;
vec3 dir = vec3(lightPos.x - posVarying.x, lightPos.y - posVarying.y, lightPos.z - posVarying.z);
vec3 norm = normalize(normalVarying);
float atten = dot(dir,dir);
atten = clamp(lightPos.w/atten,0.0,1.0);
float intensity = dot(normalize(dir),norm);
intensity = clamp(intensity,0.0,1.0);
vec3 lightColor = agk_PLightColor.rgb * intensity * atten;
vec3 shadowColor = agk_PLightColor.rgb * 0;
bool inTheShadow = false;
if (intensity * atten > 0.05) {
float distanceToLight = length(posVarying.xy - lightPos.xy);
for (float i = distanceToLight; i > 0.0; i -= 0.1) {
vec2 uvShadow = ???
if (texture2D(texture0, uvShadow).a > 0) {
inTheShadow = true;
break;
}
}
}
if (texture2D(texture0, uvVarying).a == 0) {
if (inTheShadow == true) {
gl_FragColor = texture2D(texture1, uvVarying) * vec4(shadowColor, 1) * agk_ObjColor;
}
else {
gl_FragColor = texture2D(texture1, uvVarying) * vec4(lightColor, 1) * agk_ObjColor;
}
}
else {
gl_FragColor = texture2D(texture0, uvVarying) * agk_ObjColor;
}
}
So, this is the part where I have some troubles :
bool inTheShadow = false;
if (intensity * atten > 0.05) {
float distanceToLight = length(posVarying.xy - lightPos.xy);
for (float i = distanceToLight; i > 0.0; i -= 0.1) {
vec2 uvShadow = ???
if (texture2D(texture0, uvShadow).a > 0) {
inTheShadow = true;
break;
}
}
}
I first check if I'm in the light radius with intensity * atten > 0.05
Then I get the distance from the current fragment to the light position.
And then, I make a for loop, to check each fragment between the current fragment and the light position. I tried some calculations to get the current fragment, but with no success.
So, any idea on how I can calculate the uvShadow in my loop ?
I hope I'm using the good variables too, cause in the last part of my code, where I use gl_FragColor, I'm using uvVarying to get the current fragment (If i'm not mistaken), but to get the light distance, I had to calculate the length between posVarying and lightPos and not between uvVarying and lightPos (I made a test, where the further I was from the light, the more red it became, and with posVarying, it made me a circle with gradient around my player (lightPos) but when I used uvVarying, the circle was only one color, and it was more or less red, when I was approaching my player to the center of the screen).
Thanks and best regards,
Max
When you access a texture through texture2D() you use normalised coordinates. I.e. numbers that go from (0.0, 0.0) to (1.0, 1.0). So you need to convert your world positions to this normalised space. So something like:
vec2 uvShadow = posVarying.xy + ((distanceToLight / 0.1) * i * (posVarying.xy - lightPos.xy));
// Take uvShadow from world space to view space, this is -1.0 to 1.0
uvShadow *= mat2(inverse(agk_View)); // This could be optimized if you are using orthographic projection
// Now take it to texture space
uvShadow += 0.5;
uvShadow *= 0.5;
I'm using texture blending in my terrain's fragment shader to blend from one texture to the next. Right at the seam between using only my grass texture and blending between dirt/grass or snow/grass textures, the mipmaps seem to cause an ugly seam (see photo below). Disabling mipmapping fixes the problem but makes my terrain very grainy/ugly at a distance. Is there a way to eliminate this seam without disabling mipmapping?
terrain-vs.glsl:
precision mediump float;
attribute vec3 Position;
attribute vec2 TextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec2 texCoord;
varying float y;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(Position, 1.0);
texCoord = TextureCoord;
y = Position.y;
}
terrain-fs.glsl:
precision mediump float;
uniform sampler2D dirt_texture;
uniform sampler2D grass_texture;
uniform sampler2D snow_texture;
varying vec2 texCoord;
varying float y;
void main(void) {
if (y < -5.0) {
gl_FragColor = texture2D(dirt_texture, texCoord);
} else if (y < 0.0) {
gl_FragColor = mix(
texture2D(dirt_texture, texCoord),
texture2D(grass_texture, texCoord),
(y + 5.0) / 5.0
);
} else if (y < 3.0) {
gl_FragColor = texture2D(grass_texture, texCoord);
} else if (y < 5.0) {
gl_FragColor = mix(
texture2D(grass_texture, texCoord),
texture2D(snow_texture, texCoord),
(y - 3.0) / 2.0
);
} else {
gl_FragColor = texture2D(snow_texture, texCoord);
}
}
TextureManager::initialize
gl.bindTexture(gl.TEXTURE_2D, texture.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
Configuration:
Windows 7 Pro SP1
Google Chrome 24.0.1312.57 m
NVIDIA GTX 680
normal view
zoomed in
Explanation why the original fragment shader produces artifacts
GPU needs to know gradients to sample a texture. You either supply the gradients explicitly (textureGrad) or you let GPU to compute the gradients for you. The automatically computed gradient are computed using local differencing. See function dFdx (http://www.opengl.org/sdk/docs/manglsl/xhtml/dFdx.xml) for details.
Computing derivatives produces undefined result when the function evaluates for one pixel but not for nearby pixels (non uniform control flow).
The shader in Answer 1 works well because the textures are always sampled, no matter if their result is used or not.
More info is here http://www.opengl.org/wiki/Sampler_(GLSL)#Non-uniform_flow_control
To the best of my knowledge, the following fragment shader is basically identical to the one I supplied above, but apparently mix does funky things when the parameter passed in for a is near 1.0 or 0.0. Not using mix and manually mixing the shaders seemed to fix this.
Updated fragment shader:
precision mediump float;
uniform sampler2D dirt_texture;
uniform sampler2D grass_texture;
uniform sampler2D snow_texture;
varying vec2 texCoord;
varying float y;
void main(void) {
vec4 dirt = texture2D(dirt_texture, texCoord);
vec4 grass = texture2D(grass_texture, texCoord);
vec4 snow = texture2D(snow_texture, texCoord);
float dirt_weight = 0.0;
float grass_weight = 0.0;
float snow_weight = 0.0;
if (y < -5.0) {
dirt_weight = 1.0;
} else if (y < 0.0) {
grass_weight = (y + 5.0) / 5.0;
dirt_weight = 1.0 - grass_weight;
} else if (y < 3.0) {
grass_weight = 1.0;
} else if (y < 5.0) {
snow_weight = (y - 3.0) / 2.0;
grass_weight = 1.0 - snow_weight;
} else {
snow_weight = 1.0;
}
gl_FragColor = dirt * dirt_weight + grass * grass_weight + snow * snow_weight;
}