Description:
When attempting to build a custom WGSL shader with createShaderMaterial containing an array of custom structural uniforms,
the resulting module throws a compilation crash during the fragment stage.
The framework successfully appends the uniform block types into the top of the shader files,
but it does not declare a global variable wrapper instance for that block in the fragment scope,
resulting in an unresolved value compiler error.
Steps to Reproduce:
Initialize a standard shader material using custom uniforms configurations:
const mat = createShaderMaterial({
name: 'smat_PARAGRAPH',
vertexSource: `@vertex fn mainVertex(input: VertexInput) -> VertexOutput { ... }`,
fragmentSource: `
@fragment fn mainFragment(input: VertexOutput) -> @location(0) vec4<f32> {
// Fails regardless of whether we call 'material', 'materialUniforms', or 'smat_PARAGRAPH'
return materialUniforms.uFrontFaceColor;
}
`,
attributes: ['position', 'normal', 'uv', 'color'],
uniforms: [
'world', 'worldViewProjection', 'cameraPosition',
{ name: 'uFrontFaceColor', type: 'vec4<f32>', defaultValue: [0,0,0,1] },
..more values
],
samplers: ['uDiffuseTexture', 'uDiffuseBackground'],
});
Error Log: Chrome:
Error while parsing WGSL: :57:25 error: unresolved value 'materialUniforms'
baseColor = materialUniforms.uFrontFaceColor;
^^^^^^^^^^^^^^^^
- While calling [Device].CreateShaderModule([ShaderModuleDescriptor "smat_PARAGRAPH-fragment"]).
Expected Behavior:
The factory should automatically expose custom uniform objects under a uniform token binding identifier (like material or materialUniforms)
within the fragment shader program workspace.
Complete log from ‘mat’ above
{
"name": "smat_PARAGRAPH",
"vertexSource": "\n struct VertexOutput {\n @builtin(position) gl_Position: vec4<f32>,\n @location(0) vNormalW: vec3<f32>,\n @location(1) vUV: vec2<f32>,\n @location(2) vFaceId: f32,\n @location(3) vPositionW: vec3<f32>,\n };\n\n @vertex fn mainVertex(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n let aFaceId = input.color.x; \n output.vFaceId = aFaceId;\n output.vUV = input.uv;\n \n // Always use shaderSystem for framework transformation matrices\n let worldPosVec4 = shaderSystem.world * vec4<f32>(input.position, 1.0);\n output.vPositionW = worldPosVec4.xyz;\n \n var localNormal = vec3<f32>(0.0);\n if (aFaceId < 0.5) {\n localNormal = normalize(vec3<f32>(input.normal.x * 0.2, input.normal.y * 0.2, -1.0));\n } else if (aFaceId < 1.5) {\n localNormal = normalize(vec3<f32>(input.normal.x, input.normal.y, -0.4));\n } else if (aFaceId < 2.5) {\n localNormal = vec3<f32>(0.0, 0.0, 1.0);\n } else {\n localNormal = vec3<f32>(0.0, 0.0, -1.0);\n }\n \n output.vNormalW = normalize((shaderSystem.world * vec4<f32>(localNormal, 0.0)).xyz);\n output.gl_Position = shaderSystem.worldViewProjection * worldPosVec4;\n return output;\n }",
"fragmentSource": "\n struct VertexOutput {\n @builtin(position) gl_Position: vec4<f32>,\n @location(0) vNormalW: vec3<f32>,\n @location(1) vUV: vec2<f32>,\n @location(2) vFaceId: f32,\n @location(3) vPositionW: vec3<f32>,\n };\n\n @fragment fn mainFragment(input: VertexOutput) -> @location(0) vec4<f32> {\n var baseColor = vec4<f32>(1.0);\n var isBg = 0.0;\n \n // FIX: Access properties via the framework-injected materialUniforms block variable\n if (input.vFaceId < 0.5) {\n baseColor = materialUniforms.uFrontFaceColor;\n } else if (input.vFaceId < 1.5) {\n baseColor = materialUniforms.uSideWallColor;\n } else if (input.vFaceId < 2.5) {\n baseColor = materialUniforms.uBackFaceColor;\n } else if (input.vFaceId < 3.5) {\n baseColor = materialUniforms.uBackgroundColor;\n isBg = 1.0;\n } else {\n baseColor = materialUniforms.uCornerColor;\n }\n \n if (isBg > 0.5) {\n if (materialUniforms.uHasBackground > 0.5) {\n baseColor = baseColor * textureSample(uDiffuseBackground, uDiffuseBackgroundSampler, input.vUV);\n }\n } else {\n if (materialUniforms.uHasTexture > 0.5) {\n baseColor = baseColor * textureSample(uDiffuseTexture, uDiffuseTextureSampler, input.vUV);\n }\n }\n \n var finalRGB = baseColor.rgb;\n \n if (materialUniforms.uDisableLighting < 0.5) {\n let lightDir = normalize(materialUniforms.uLightPos - input.vPositionW);\n let diff = max(dot(input.vNormalW, lightDir), 0.0);\n \n let ambientComponent = baseColor.rgb * materialUniforms.uAmbientColor;\n let diffuseComponent = baseColor.rgb * materialUniforms.uDiffuseColor * diff * materialUniforms.uLightColor;\n \n finalRGB = ambientComponent + diffuseComponent;\n \n if (isBg < 0.5) {\n let viewDir = normalize(materialUniforms.uCameraPos - input.vPositionW);\n let reflectDir = reflect(-lightDir, input.vNormalW);\n let spec = pow(max(dot(viewDir, reflectDir), 0.0), materialUniforms.uSpecularPower);\n finalRGB = finalRGB + (materialUniforms.uSpecularColor * spec * materialUniforms.uLightColor);\n }\n }\n \n if (input.vFaceId >= 0.5 && input.vFaceId < 1.5) {\n finalRGB = finalRGB + materialUniforms.uEmissiveColor;\n }\n \n if ((baseColor.a * materialUniforms.uAlpha) < 0.1) {\n discard;\n }\n \n if (input.vFaceId < 0.5 && materialUniforms.uEffectIntensity > 0.01) {\n let t = materialUniforms.uTime * materialUniforms.uEffectSpeed;\n let wave = sin(input.vPositionW.x * 2.0 + t) * cos(input.vPositionW.y * 2.0 + t);\n let neon = vec3<f32>(0.0, 1.0, 0.8);\n finalRGB = mix(finalRGB, neon, wave * materialUniforms.uEffectIntensity);\n }\n \n return vec4<f32>(finalRGB, baseColor.a * materialUniforms.uAlpha);\n }",
"attributes": [
"position",
"normal",
"uv",
"color"
],
"uniformDecls": [
{
"name": "world",
"type": "mat4x4<f32>"
},
{
"name": "worldViewProjection",
"type": "mat4x4<f32>"
},
{
"name": "cameraPosition",
"type": "vec3<f32>"
},
{
"name": "uLightPos",
"type": "vec3<f32>",
"defaultValue": [
5,
10,
5
]
},
{
"name": "uCameraPos",
"type": "vec3<f32>",
"defaultValue": [
0,
0,
0
]
},
{
"name": "uLightColor",
"type": "vec3<f32>",
"defaultValue": [
1,
1,
1
]
},
{
"name": "uFrontFaceColor",
"type": "vec4<f32>",
"defaultValue": [
0.9607843137254902,
0.23921568627450981,
0.4470588235294118,
1
]
},
{
"name": "uSideWallColor",
"type": "vec4<f32>",
"defaultValue": [
0.9607843137254902,
0.23921568627450981,
0.4470588235294118,
1
]
},
{
"name": "uBackFaceColor",
"type": "vec4<f32>",
"defaultValue": [
0.9607843137254902,
0.23921568627450981,
0.4470588235294118,
1
]
},
{
"name": "uBackgroundColor",
"type": "vec4<f32>",
"defaultValue": [
1,
1,
1,
1
]
},
{
"name": "uCornerColor",
"type": "vec4<f32>",
"defaultValue": [
1,
1,
1,
1
]
},
{
"name": "uEmissiveColor",
"type": "vec3<f32>",
"defaultValue": [
0,
0,
0
]
},
{
"name": "uAmbientColor",
"type": "vec3<f32>",
"defaultValue": [
1,
1,
0.8784313725490196
]
},
{
"name": "uDiffuseColor",
"type": "vec3<f32>",
"defaultValue": [
1,
1,
1
]
},
{
"name": "uSpecularColor",
"type": "vec3<f32>",
"defaultValue": [
1,
1,
1
]
},
{
"name": "uSpecularPower",
"type": "f32",
"defaultValue": 16
},
{
"name": "uAlpha",
"type": "f32",
"defaultValue": 1
},
{
"name": "uDisableLighting",
"type": "f32",
"defaultValue": 0
},
{
"name": "uHasTexture",
"type": "f32",
"defaultValue": 1
},
{
"name": "uHasBackground",
"type": "f32",
"defaultValue": 0
},
{
"name": "uTime",
"type": "f32",
"defaultValue": 0
},
{
"name": "uEffectIntensity",
"type": "f32",
"defaultValue": 0
},
{
"name": "uEffectSpeed",
"type": "f32",
"defaultValue": 1
}
],
"samplerDecls": [
{
"name": "uDiffuseTexture",
"sampleType": "float"
},
{
"name": "uDiffuseBackground",
"sampleType": "float"
}
],
"storageBufferDecls": [],
"defines": [],
"needAlphaBlending": true,
"blendMode": "additive",
"transmissive": false,
"needAlphaTesting": false,
"backFaceCulling": false,
"depthWrite": true,
"depthCompare": "greater-equal",
"depthOnlyFragment": false,
"depthBias": 0,
"depthBiasSlopeScale": 0,
"_uboVersion": 0,
"_uniformValues": {},
"_textureSlots": {},
"_storageBufferSlots": {},
"_uniformVersion": 0,
"_resourceVersion": 2,
"_shaderDevice": {},
"_shaderBindings": {
"group1BGL": {},
"systemSpec": {
"_totalBytes": 144,
"_offsets": {},
"_structBody": "world: mat4x4<f32>,\nworldViewProjection: mat4x4<f32>,\ncameraPosition: vec3<f32>,"
},
"customSpec": {
"_totalBytes": 224,
"_offsets": {},
"_structBody": "uLightPos: vec3<f32>,\nuCameraPos: vec3<f32>,\nuLightColor: vec3<f32>,\nuFrontFaceColor: vec4<f32>,\nuSideWallColor: vec4<f32>,\nuBackFaceColor: vec4<f32>,\nuBackgroundColor: vec4<f32>,\nuCornerColor: vec4<f32>,\nuEmissiveColor: vec3<f32>,\nuAmbientColor: vec3<f32>,\nuDiffuseColor: vec3<f32>,\nuSpecularColor: vec3<f32>,\nuSpecularPower: f32,\nuAlpha: f32,\nuDisableLighting: f32,\nuHasTexture: f32,\nuHasBackground: f32,\nuTime: f32,\nuEffectIntensity: f32,\nuEffectSpeed: f32,"
},
"vertexBuffers": [
{
"arrayStride": 12,
"attributes": [
{
"shaderLocation": 0,
"offset": 0,
"format": "float32x3"
}
]
},
{
"arrayStride": 12,
"attributes": [
{
"shaderLocation": 1,
"offset": 0,
"format": "float32x3"
}
]
},
{
"arrayStride": 8,
"attributes": [
{
"shaderLocation": 2,
"offset": 0,
"format": "float32x2"
}
]
},
{
"arrayStride": 16,
"attributes": [
{
"shaderLocation": 3,
"offset": 0,
"format": "float32x4"
}
]
}
],
"pipelines": {},
"_pipelineLayout": {}
},
"_shaderCustomSpec": {
"_totalBytes": 224,
"_offsets": {},
"_structBody": "uLightPos: vec3<f32>,\nuCameraPos: vec3<f32>,\nuLightColor: vec3<f32>,\nuFrontFaceColor: vec4<f32>,\nuSideWallColor: vec4<f32>,\nuBackFaceColor: vec4<f32>,\nuBackgroundColor: vec4<f32>,\nuCornerColor: vec4<f32>,\nuEmissiveColor: vec3<f32>,\nuAmbientColor: vec3<f32>,\nuDiffuseColor: vec3<f32>,\nuSpecularColor: vec3<f32>,\nuSpecularPower: f32,\nuAlpha: f32,\nuDisableLighting: f32,\nuHasTexture: f32,\nuHasBackground: f32,\nuTime: f32,\nuEffectIntensity: f32,\nuEffectSpeed: f32,"
},
"_shaderCustomUbo": {},
"_shaderCustomData": {},
"_shaderCustomBytes": {
"0": 0,
"1": 0,
"2": 160,
"3": 64,
"4": 0,
"5": 0,
"6": 32,
"7": 65,
"8": 0,
"9": 0,
"10": 160,
"11": 64,
"12": 0,
"13": 0,
"14": 0,
"15": 0,
"16": 0,
"17": 0,
"18": 0,
"19": 0,
"20": 0,
"21": 0,
"22": 0,
"23": 0,
"24": 0,
"25": 0,
"26": 0,
"27": 0,
"28": 0,
"29": 0,
"30": 0,
"31": 0,
"32": 0,
"33": 0,
"34": 128,
"35": 63,
"36": 0,
"37": 0,
"38": 128,
"39": 63,
"40": 0,
"41": 0,
"42": 128,
"43": 63,
"44": 0,
"45": 0,
"46": 0,
"47": 0,
"48": 246,
"49": 245,
"50": 117,
"51": 63,
"52": 245,
"53": 244,
"54": 116,
"55": 62,
"56": 229,
"57": 228,
"58": 228,
"59": 62,
"60": 0,
"61": 0,
"62": 128,
"63": 63,
"64": 246,
"65": 245,
"66": 117,
"67": 63,
"68": 245,
"69": 244,
"70": 116,
"71": 62,
"72": 229,
"73": 228,
"74": 228,
"75": 62,
"76": 0,
"77": 0,
"78": 128,
"79": 63,
"80": 246,
"81": 245,
"82": 117,
"83": 63,
"84": 245,
"85": 244,
"86": 116,
"87": 62,
"88": 229,
"89": 228,
"90": 228,
"91": 62,
"92": 0,
"93": 0,
"94": 128,
"95": 63,
"96": 0,
"97": 0,
"98": 128,
"99": 63,
"100": 0,
"101": 0,
"102": 128,
"103": 63,
"104": 0,
"105": 0,
"106": 128,
"107": 63,
"108": 0,
"109": 0,
"110": 128,
"111": 63,
"112": 0,
"113": 0,
"114": 128,
"115": 63,
"116": 0,
"117": 0,
"118": 128,
"119": 63,
"120": 0,
"121": 0,
"122": 128,
"123": 63,
"124": 0,
"125": 0,
"126": 128,
"127": 63,
"128": 0,
"129": 0,
"130": 0,
"131": 0,
"132": 0,
"133": 0,
"134": 0,
"135": 0,
"136": 0,
"137": 0,
"138": 0,
"139": 0,
"140": 0,
"141": 0,
"142": 0,
"143": 0,
"144": 0,
"145": 0,
"146": 128,
"147": 63,
"148": 0,
"149": 0,
"150": 128,
"151": 63,
"152": 225,
"153": 224,
"154": 96,
"155": 63,
"156": 0,
"157": 0,
"158": 0,
"159": 0,
"160": 0,
"161": 0,
"162": 128,
"163": 63,
"164": 0,
"165": 0,
"166": 128,
"167": 63,
"168": 0,
"169": 0,
"170": 128,
"171": 63,
"172": 0,
"173": 0,
"174": 0,
"175": 0,
"176": 0,
"177": 0,
"178": 128,
"179": 63,
"180": 0,
"181": 0,
"182": 128,
"183": 63,
"184": 0,
"185": 0,
"186": 128,
"187": 63,
"188": 0,
"189": 0,
"190": 128,
"191": 65,
"192": 0,
"193": 0,
"194": 128,
"195": 63,
"196": 0,
"197": 0,
"198": 0,
"199": 0,
"200": 0,
"201": 0,
"202": 128,
"203": 63,
"204": 0,
"205": 0,
"206": 0,
"207": 0,
"208": 0,
"209": 0,
"210": 0,
"211": 0,
"212": 0,
"213": 0,
"214": 0,
"215": 0,
"216": 0,
"217": 0,
"218": 128,
"219": 63,
"220": 0,
"221": 0,
"222": 0,
"223": 0
},
"_shaderCustomVersion": 0
}
```;