# Shader preprocessor: #if !X is always false (a define negated without defined())

**URL:** <https://forum.babylonjs.com/t/shader-preprocessor-if-x-is-always-false-a-define-negated-without-defined/64479>\
**Category:** Bugs\
**Created:** [October 31, 2026, 12:40am UTC](https://forum.babylonjs.com/t/shader-preprocessor-if-x-is-always-false-a-define-negated-without-defined/64479 "2026-10-31T00:40:13Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Enrique\_Velasquez](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/enrique_velasquez/32/50550_2.png) [@Enrique\_Velasquez](https://forum.babylonjs.com/u/Enrique_Velasquez)\
**Post date:** [October 31, 2026, 12:40am UTC](https://forum.babylonjs.com/t/shader-preprocessor-if-x-is-always-false-a-define-negated-without-defined/64479/1 "2026-10-31T00:40:13Z")

</div>

The shader preprocessor reads `#if X` as `#if defined(X)`, but `#if !X` is taken for a define named “!X”, which never exists, so the branch is always dropped, without a warning. `#if !defined(X)` works. In WGSL, where Babylon’s preprocessor is the only one, it is easy to write `#if !X` and get the other branch every time: we hit it in a WGSL material plugin, whose variant without the define never ran, and two of our measurements compared the same variant twice before we saw it.

Playground: [https://playground.babylonjs.com/#T2ZIJX#0](https://playground.babylonjs.com/#T2ZIJX#0). A plane is drawn red when `X` is not defined and green when it is. On 9.33.0 it is green in both cases, in WGSL and in GLSL alike.

The cause is in `ExtractOperation` (`shaderProcessor.ts`): an operand without `defined(` and without a comparison becomes `ShaderDefineIsDefinedOperator(expression)`, with the `!` kept in the name. [#19098](https://github.com/BabylonJS/Babylon.js/pull/19098) reads it as `!defined(X)`, in `&&` and `||` too, with unit tests; `#if X`, `#if !defined(X)` and the comparisons are unchanged.

Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me.

---

<div class="post-metadata">

**Author:** ![nasimiasl](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/nasimiasl/32/34_2.png) [@nasimiasl](https://forum.babylonjs.com/u/nasimiasl)\
**Post date:** [October 31, 2026, 3:10am UTC](https://forum.babylonjs.com/t/shader-preprocessor-if-x-is-always-false-a-define-negated-without-defined/64479/2 "2026-10-31T03:10:56Z")

</div>

normaly ! must apply to the valid value not logical term try use !(X)

---

<div class="post-metadata">

**Author:** ![metin6176](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/metin6176/32/52356_2.png) [@metin6176](https://forum.babylonjs.com/u/metin6176)\
**Post date:** [October 31, 2026, 6:17pm UTC](https://forum.babylonjs.com/t/shader-preprocessor-if-x-is-always-false-a-define-negated-without-defined/64479/3 "2026-10-31T18:17:58Z")

</div>

Good catch, silent failures like this are the worst to track down. Until the fix is merged, #if !defined(X) is the safe form. A warning when a define name starts with an exclamation mark would also have saved a lot of time here.

---

<div class="post-metadata">

**Author:** ![Enrique\_Velasquez](https://sea2.discourse-cdn.com/flex024/user_avatar/forum.babylonjs.com/enrique_velasquez/32/50550_2.png) [@Enrique\_Velasquez](https://forum.babylonjs.com/u/Enrique_Velasquez)\
**Post date:** [November 2, 2026, 6:26pm UTC](https://forum.babylonjs.com/t/shader-preprocessor-if-x-is-always-false-a-define-negated-without-defined/64479/4 "2026-11-02T18:26:33Z")

</div>

Thanks for the suggestion. We tried it, and in Babylon’s preprocessor `#if !(X)` is not a way around it: it is read as `#if X`, so it keeps the opposite branch, again without a warning. `ShaderDefineExpression.infixToPostfix` drops the operand it has gathered when it meets an opening parenthesis (`if (c === "(") { operand = ""; ... }`), and the `!` in front of `(X)` is that operand. `#if !(defined(X))` is read as `#if defined(X)` the same way.

The branch the preprocessor keeps on 9.33.1:

| condition | `X` not defined | `X` defined |
| --- | --- | --- |
| `#if !X` | `#else` | `#else` |
| `#if !(X)` | `#else` | `#if` |
| `#if !defined(X)` | `#if` | `#else` |

With #19098, `#if !X` keeps `#if` and then `#else`, as `#if !defined(X)` does. `#if !(X)` does not change: #19098 reads the `!` of an operand in `ExtractOperation`, and with a parenthesis the `!` is already gone by then. So for now `#if !defined(X)` is the form to use, as metin6176 says.

On the warning: once `!X` is read as `!defined(X)`, there is no define named `!X` left to warn about, but `!(` is the same kind of silent failure. If it is welcome, we can send the `!` in front of a parenthesis as a separate PR, rather than widen #19098, which is already approved.

Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me.
