Godot version: 3.1.1.stable.official
OS/device including version: Ubuntu 16.04.6 LTS
Issue description:
It appears that usage of SCREEN_UV inside a canvas_item shader changes how those sprites are rendered in a way that breaks lighting (for my purposes).
I'm not precisely sure how to describe the behavior, but here's my use case:
I'm putting together a 2D platformer using the tilemap system. However, in order to add detail to the tilemaps, I've decided to use a masking system, where a mask for the tilemap is first rendered onto a Viewport, and then the tile shader reads from that mask to see whether to discard the fragment.
However, it appears that Light2Ds will still illuminate the discarded fragments, and I haven't found a way to stop that from happening.
Here's an image demonstrating the behavior:

Upon further investigation, I was able to create a situation where a shader that reads from SCREEN_UV has a similar problem, while a shader that reads from UV does not. I have attached a demo project. In this demo, strangely enough, reading from SCREEN_UV completely disables lighting, which is seemingly not related to the behavior in my real project.
I believe this is, incidentally, related to an issue I opened before, so I might have a bit more understanding of why it is happening. I believe that screen-reading shaders are likely rendered differently from non-screen-reading shaders, and this is for some justifiable reasons, from my understanding.
However, it is important to note that just because a shader reads from SCREEN_UV does not mean it is a screen-reading shader. The example project I have attached should exemplify this.
So my suggestion is that reading from SCREEN_UV should not impact how a shader is rendered.
Of course, for my real use case, there may be additional factors causing the problem. But I think the demo project indicates that the SCREEN_UV issue does exist independently in any case.
Steps to reproduce:
Create a shader that does something. Change it to use SCREEN_UV somewhere. Observe if this changes how it interacts with lights.
Minimal reproduction project:
screen-uv-example.zip
Note that this is only reproducible in GLES3.
_edit:_ Its not as simple as just using SCREEN_UV. The following code doesn't break rendering in the same way but still uses SCREEN_UV
void fragment() {
float x = SCREEN_UV.x;
if(rand_int(UV) == x) {
discard;
}
}
After playing around with it a bit more I found two more distinct behaviors, created by these shaders:
bool threshold(float x, float y) {
return abs(x - y) < 0.1;
}
void fragment() {
if(threshold(rand(SCREEN_UV), 0.5)) {
discard;
}
}
bool threshold(float x, float y) {
return abs(x - y) < 0.1;
}
void fragment() {
float x = SCREEN_UV.x;
if(threshold(rand(UV), x)) {
discard;
}
}
I can't imagine this information is particularly illuminating, but I guess I might as well post it.
Here's an additional project with everything I've looked at:
screen-uv-example-2.zip
If I have more time I might try to reproduce the structures in my real project to see if they have the same behaviors. They were incredibly strange: if I changed the color I used for masking in the shaders to the background color of the mask viewport, they would no longer have the same "illuminating discarded pixels" bug, but would rather display the "no illumination whatsoever" bug.
After playing around with it a bit more I found two more distinct behaviors, created by these shaders:
bool threshold(float x, float y) { return abs(x - y) < 0.1; } void fragment() { if(threshold(rand(SCREEN_UV), 0.5)) { discard; } }bool threshold(float x, float y) { return abs(x - y) < 0.1; } void fragment() { float x = SCREEN_UV.x; if(threshold(rand(UV), x)) { discard; } }I can't imagine this information is particularly illuminating, but I guess I might as well post it.
Here's an additional project with everything I've looked at:
screen-uv-example-2.zipIf I have more time I might try to reproduce the structures in my real project to see if they have the same behaviors. They were incredibly strange: if I changed the color I used for masking in the shaders to the background color of the mask viewport, they would no longer have the same "illuminating discarded pixels" bug, but would rather display the "no illumination whatsoever" bug.
Is this not supposed to work with GLES2? I got many errors along with some long shader code many times(probably endlessly). Here's the part of it which repeats again and again(note this is not the start of the log but still shows where it repeats):
ERROR: _display_error_with_code: CanvasShaderGLES2: Fragment shader compilation failed:
ERROR: 4:10: 'round' : no matching overloaded function found (using implicit conversion)
ERROR: 4:10: 'round' : function is not known
At: drivers\gles2\shader_gles2.cpp:129
ERROR: ShaderGLES2::get_current_version: Method/Function Failed, returning: 0
At: drivers\gles2\shader_gles2.cpp:360
ERROR: ShaderGLES2::bind: Condition ' !version ' is true. returned: false
At: drivers\gles2\shader_gles2.cpp:88
1: #version 120
2: #define USE_GLES_OVER_GL
3: #define USE_TEXTURE_RECT
4: #define USE_LIGHTING
5:
6: // texture2DLodEXT and textureCubeLodEXT are fragment shader specific.
7: // Do not copy these defines in the vertex section.
8: #ifndef USE_GLES_OVER_GL
9: #ifdef GL_EXT_shader_texture_lod
10: #extension GL_EXT_shader_texture_lod : enable
11: #define texture2DLod(img, coord, lod) texture2DLodEXT(img, coord, lod)
12: #define textureCubeLod(img, coord, lod) textureCubeLodEXT(img, coord, lod)
13: #endif
14: #endif // !USE_GLES_OVER_GL
15:
16: #ifdef GL_ARB_shader_texture_lod
17: #extension GL_ARB_shader_texture_lod : enable
18: #endif
19:
20: #if !defined(GL_EXT_shader_texture_lod) && !defined(GL_ARB_shader_texture_lod)
21: #define texture2DLod(img, coord, lod) texture2D(img, coord, lod)
22: #define textureCubeLod(img, coord, lod) textureCube(img, coord, lod)
23: #endif
24:
25: #ifdef USE_GLES_OVER_GL
26: #define lowp
27: #define mediump
28: #define highp
29: #else
30: #if defined(USE_HIGHP_PRECISION)
31: precision highp float;
32: precision highp int;
33: #else
34: precision mediump float;
35: precision mediump int;
36: #endif
37: #endif
38:
39: uniform sampler2D color_texture; // texunit:-1
40: /* clang-format on */
41: uniform highp vec2 color_texpixel_size;
42: uniform mediump sampler2D normal_texture; // texunit:-2
43:
44: varying mediump vec2 uv_interp;
45: varying mediump vec4 color_interp;
46:
47: uniform highp float time;
48:
49: uniform vec4 final_modulate;
50:
51: #ifdef SCREEN_TEXTURE_USED
52:
53: uniform sampler2D screen_texture; // texunit:-4
54:
55: #endif
56:
57: #ifdef SCREEN_UV_USED
58:
59: uniform vec2 screen_pixel_size;
60:
61: #endif
62:
63: #ifdef USE_LIGHTING
64:
65: uniform highp mat4 light_matrix;
66: uniform highp mat4 light_local_matrix;
67: uniform highp mat4 shadow_matrix;
68: uniform highp vec4 light_color;
69: uniform highp vec4 light_shadow_color;
70: uniform highp vec2 light_pos;
71: uniform highp float shadowpixel_size;
72: uniform highp float shadow_gradient;
73: uniform highp float light_height;
74: uniform highp float light_outside_alpha;
75: uniform highp float shadow_distance_mult;
76:
77: uniform lowp sampler2D light_texture; // texunit:-4
78: varying vec4 light_uv_interp;
79: varying vec2 transformed_light_uv;
80:
81: varying vec4 local_rot;
82:
83: #ifdef USE_SHADOWS
84:
85: uniform highp sampler2D shadow_texture; // texunit:-5
86: varying highp vec2 pos;
87:
88: #endif
89:
90: const bool at_light_pass = true;
91: #else
92: const bool at_light_pass = false;
93: #endif
94:
95: uniform bool use_default_normal;
96:
97: /* clang-format off */
98:
99: float m_rand(in vec2 m_co)
100: {
101: return fract((sin(dot(m_co.xy, vec2(12.9898,78.233))) * 43758.5));
102: }
103:
104: float m_rand_int(in vec2 m_uv)
105: {
106: return round(m_rand(m_uv));
107: }
108:
109:
110: /* clang-format on */
111:
112: void light_compute(
113: inout vec4 light,
114: inout vec2 light_vec,
115: inout float light_height,
116: inout vec4 light_color,
117: vec2 light_uv,
118: inout vec4 shadow_color,
119: vec3 normal,
120: vec2 uv,
121: #if defined(SCREEN_UV_USED)
122: vec2 screen_uv,
123: #endif
124: vec4 color) {
125:
126: #if defined(USE_LIGHT_SHADER_CODE)
127:
128: /* clang-format off */
129:
130:
131: /* clang-format on */
132:
133: #endif
134: }
135:
136: void main() {
137:
138: vec4 color = color_interp;
139: vec2 uv = uv_interp;
140: #ifdef USE_FORCE_REPEAT
141: //needs to use this to workaround GLES2/WebGL1 forcing tiling that textures that don't support it
142: uv = mod(uv, vec2(1.0, 1.0));
143: #endif
144:
145: #if !defined(COLOR_USED)
146: //default behavior, texture by color
147: color *= texture2D(color_texture, uv);
148: #endif
149:
150: #ifdef SCREEN_UV_USED
151: vec2 screen_uv = gl_FragCoord.xy * screen_pixel_size;
152: #endif
153:
154: vec3 normal;
155:
156: #if defined(NORMAL_USED)
157:
158: bool normal_used = true;
159: #else
160: bool normal_used = false;
161: #endif
162:
163: if (use_default_normal) {
164: normal.xy = texture2D(normal_texture, uv).xy * 2.0 - 1.0;
165: normal.z = sqrt(1.0 - dot(normal.xy, normal.xy));
166: normal_used = true;
167: } else {
168: normal = vec3(0.0, 0.0, 1.0);
169: }
170:
171: {
172: float normal_depth = 1.0;
173:
174: #if defined(NORMALMAP_USED)
175: vec3 normal_map = vec3(0.0, 0.0, 1.0);
176: normal_used = true;
177: #endif
178:
179: /* clang-format off */
180: {
181: if ((m_rand_int(uv) == 0.0))
182: {
183: {
184: discard; }
185: ;
186: }
187: }
188:
189:
190: /* clang-format on */
191:
192: #if defined(NORMALMAP_USED)
193: normal = mix(vec3(0.0, 0.0, 1.0), normal_map * vec3(2.0, -2.0, 1.0) - vec3(1.0, -1.0, 0.0), normal_depth);
194: #endif
195: }
196: color *= final_modulate;
197:
198: #ifdef USE_LIGHTING
199:
200: vec2 light_vec = transformed_light_uv;
201:
202: if (normal_used) {
203: normal.xy = mat2(local_rot.xy, local_rot.zw) * normal.xy;
204: }
205:
206: float att = 1.0;
207:
208: vec2 light_uv = light_uv_interp.xy;
209: vec4 light = texture2D(light_texture, light_uv);
210:
211: if (any(lessThan(light_uv_interp.xy, vec2(0.0, 0.0))) || any(greaterThanEqual(light_uv_interp.xy, vec2(1.0, 1.0)))) {
212: color.a *= light_outside_alpha; //invisible
213:
214: } else {
215: float real_light_height = light_height;
216: vec4 real_light_color = light_color;
217: vec4 real_light_shadow_color = light_shadow_color;
218:
219: #if defined(USE_LIGHT_SHADER_CODE)
220: //light is written by the light shader
221: light_compute(
222: light,
223: light_vec,
224: real_light_height,
225: real_light_color,
226: light_uv,
227: real_light_shadow_color,
228: normal,
229: uv,
230: #if defined(SCREEN_UV_USED)
231: screen_uv,
232: #endif
233: color);
234: #endif
235:
236: light *= real_light_color;
237:
238: if (normal_used) {
239: vec3 light_normal = normalize(vec3(light_vec, -real_light_height));
240: light *= max(dot(-light_normal, normal), 0.0);
241: }
242:
243: color *= light;
244:
245: #ifdef USE_SHADOWS
246: // Reset light_vec to compute shadows, the shadow map is created from the light origin, so it only
247: // makes sense to compute shadows from there.
248: light_vec = light_uv_interp.zw;
249:
250: float angle_to_light = -atan(light_vec.x, light_vec.y);
251: float PI = 3.14159265358979323846264;
252: /*int i = int(mod(floor((angle_to_light+7.0*PI/6.0)/(4.0*PI/6.0))+1.0, 3.0)); // +1 pq os indices estao em ordem 2,0,1 nos arrays
253: float ang*/
254:
255: float su, sz;
256:
257: float abs_angle = abs(angle_to_light);
258: vec2 point;
259: float sh;
260: if (abs_angle < 45.0 * PI / 180.0) {
261: point = light_vec;
262: sh = 0.0 + (1.0 / 8.0);
263: } else if (abs_angle > 135.0 * PI / 180.0) {
264: point = -light_vec;
265: sh = 0.5 + (1.0 / 8.0);
266: } else if (angle_to_light > 0.0) {
267:
268: point = vec2(light_vec.y, -light_vec.x);
269: sh = 0.25 + (1.0 / 8.0);
270: } else {
271:
272: point = vec2(-light_vec.y, light_vec.x);
273: sh = 0.75 + (1.0 / 8.0);
274: }
275:
276: highp vec4 s = shadow_matrix * vec4(point, 0.0, 1.0);
277: s.xyz /= s.w;
278: su = s.x * 0.5 + 0.5;
279: sz = s.z * 0.5 + 0.5;
280: //sz=lightlength(light_vec);
281:
282: highp float shadow_attenuation = 0.0;
283:
284: #ifdef USE_RGBA_SHADOWS
285:
286: #define SHADOW_DEPTH(m_tex, m_uv) dot(texture2D((m_tex), (m_uv)), vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0))
287:
288: #else
289:
290: #define SHADOW_DEPTH(m_tex, m_uv) (texture2D((m_tex), (m_uv)).r)
291:
292: #endif
293:
294: #ifdef SHADOW_USE_GRADIENT
295:
296: /* clang-format off */
297: /* GLSL es 100 doesn't support line continuation characters(backslashes) */
298: #define SHADOW_TEST(m_ofs) { highp float sd = SHADOW_DEPTH(shadow_texture, vec2(m_ofs, sh)); shadow_attenuation += 1.0 - smoothstep(sd, sd + shadow_gradient, sz); }
299:
300: #else
301:
302: #define SHADOW_TEST(m_ofs) { highp float sd = SHADOW_DEPTH(shadow_texture, vec2(m_ofs, sh)); shadow_attenuation += step(sz, sd); }
303: /* clang-format on */
304:
305: #endif
306:
307: #ifdef SHADOW_FILTER_NEAREST
308:
309: SHADOW_TEST(su);
310:
311: #endif
312:
313: #ifdef SHADOW_FILTER_PCF3
314:
315: SHADOW_TEST(su + shadowpixel_size);
316: SHADOW_TEST(su);
317: SHADOW_TEST(su - shadowpixel_size);
318: shadow_attenuation /= 3.0;
319:
320: #endif
321:
322: #ifdef SHADOW_FILTER_PCF5
323:
324: SHADOW_TEST(su + shadowpixel_size * 2.0);
325: SHADOW_TEST(su + shadowpixel_size);
326: SHADOW_TEST(su);
327: SHADOW_TEST(su - shadowpixel_size);
328: SHADOW_TEST(su - shadowpixel_size * 2.0);
329: shadow_attenuation /= 5.0;
330:
331: #endif
332:
333: #ifdef SHADOW_FILTER_PCF7
334:
335: SHADOW_TEST(su + shadowpixel_size * 3.0);
336: SHADOW_TEST(su + shadowpixel_size * 2.0);
337: SHADOW_TEST(su + shadowpixel_size);
338: SHADOW_TEST(su);
339: SHADOW_TEST(su - shadowpixel_size);
340: SHADOW_TEST(su - shadowpixel_size * 2.0);
341: SHADOW_TEST(su - shadowpixel_size * 3.0);
342: shadow_attenuation /= 7.0;
343:
344: #endif
345:
346: #ifdef SHADOW_FILTER_PCF9
347:
348: SHADOW_TEST(su + shadowpixel_size * 4.0);
349: SHADOW_TEST(su + shadowpixel_size * 3.0);
350: SHADOW_TEST(su + shadowpixel_size * 2.0);
351: SHADOW_TEST(su + shadowpixel_size);
352: SHADOW_TEST(su);
353: SHADOW_TEST(su - shadowpixel_size);
354: SHADOW_TEST(su - shadowpixel_size * 2.0);
355: SHADOW_TEST(su - shadowpixel_size * 3.0);
356: SHADOW_TEST(su - shadowpixel_size * 4.0);
357: shadow_attenuation /= 9.0;
358:
359: #endif
360:
361: #ifdef SHADOW_FILTER_PCF13
362:
363: SHADOW_TEST(su + shadowpixel_size * 6.0);
364: SHADOW_TEST(su + shadowpixel_size * 5.0);
365: SHADOW_TEST(su + shadowpixel_size * 4.0);
366: SHADOW_TEST(su + shadowpixel_size * 3.0);
367: SHADOW_TEST(su + shadowpixel_size * 2.0);
368: SHADOW_TEST(su + shadowpixel_size);
369: SHADOW_TEST(su);
370: SHADOW_TEST(su - shadowpixel_size);
371: SHADOW_TEST(su - shadowpixel_size * 2.0);
372: SHADOW_TEST(su - shadowpixel_size * 3.0);
373: SHADOW_TEST(su - shadowpixel_size * 4.0);
374: SHADOW_TEST(su - shadowpixel_size * 5.0);
375: SHADOW_TEST(su - shadowpixel_size * 6.0);
376: shadow_attenuation /= 13.0;
377:
378: #endif
379:
380: //color *= shadow_attenuation;
381: color = mix(real_light_shadow_color, color, shadow_attenuation);
382: //use shadows
383: #endif
384: }
385:
386: //use lighting
387: #endif
388:
389: gl_FragColor = color;
390: }
391:
ERROR: _display_error_with_code: CanvasShaderGLES2: Fragment shader compilation failed:
ERROR: 5:10: 'round' : no matching overloaded function found (using implicit conversion)
ERROR: 5:10: 'round' : function is not known
At: drivers\gles2\shader_gles2.cpp:129
ERROR: ShaderGLES2::get_current_version: Method/Function Failed, returning: 0
At: drivers\gles2\shader_gles2.cpp:360
ERROR: ShaderGLES2::bind: Condition ' !version ' is true. returned: false
At: drivers\gles2\shader_gles2.cpp:88
ERROR: ShaderGLES2::_get_uniform: Condition ' !version ' is true. returned: -1
At: d:\godot\compile_space\godot\drivers\gles2\shader_gles2.h:254
ERROR: ShaderGLES2::_get_uniform: Condition ' !version ' is true. returned: -1
At: d:\godot\compile_space\godot\drivers\gles2\shader_gles2.h:254
ERROR: ShaderGLES2::_get_uniform: Condition ' !version ' is true. returned: -1
At: d:\godot\compile_space\godot\drivers\gles2\shader_gles2.h:254
Might be unrelated.
GLES3:

GLES2:

That big white rect that only appears in GLES2 is puzzling me.
@Anutrix The GLES2 issue is unrelated. GLES2 doesn't have the built-in round() function, so when you try to use it in a shader it results in a compile error. https://github.com/godotengine/godot/issues/24850
I've been messing around with canvas.glsl and found something, maybe..?
If I comment out uniform vec2 screen_pixel_size; and then, to the top of main(), add vec2 screen_pixel_size = vec2(0.5 / 640.0, 0.5 / 480.0);, the bug goes away for my demo project (as these should be the proper pixel size factors).
However, this doesn't really make sense, as screen_pixel_size appears to be entirely unrelated to the lighting calculations. The only thought I had is that maybe uniforms were being written to the wrong place, but I couldn't find anything else that would indicate that was happening.
Here's my theory as to what's happening:
During the light pass, screen_pixel_size is being set to zero.
Take a look at this shader:
shader_type canvas_item;
uniform vec2 offset;
float rand(vec2 co){
return fract(sin(dot((co + offset).xy ,vec2(12.9898,78.233))) * 43758.5453);
}
vec3 rand3(vec2 co) {
float a = rand(co);
float b = rand(co + vec2(a));
float c = rand(co + vec2(b));
return vec3(a, b, c);
}
void fragment() {
COLOR.rgb = rand3(SCREEN_UV);
}
And then use the same setup as in my demo scene.
Changing the uniform vec2 offset will change which color _the entire lighted section_ takes on. This would happen if screen_uv is zero (due to being multiplied by a zero screen_pixel_size), and it also just so happens that an offset of zero results in rand3 return (0, 0, 0), for zero screen_uv, or unlit.
I just can't figure out where in the source code this bug would be coming from.
You can prove your theory by changing the value based on whether AT_LIGHT_PASS is true or false.
vec2 uv = UV;
if (AT_LIGHT_PASS) {
uv = vec2(0.0);
}
Is the same as using SCREEN_UV
There are a couple places to look for this. Most likely is rasterizer_canvas_gles3.cpp This is the file responsible for setting shader uniforms, so look for where they set USE_LIGHTING, and SCREEN_PIXEL_SIZE.
SCREEN_UV_USED is set automatically by the shader compiler when it detects that you have used SCREEN_UV. Something may be wonky with that too. So look in shader_compiler_gles3.cpp
Since this issue isn't in GLES2, it would be very useful to compare the two and see what is done differently in each file in GLES2.
Just submitted a commit to fix this! :)
Oh no me too
@TheMonsterFromTheDeep Haha! Oh no!
Most helpful comment
Oh no me too