As webGL platforms go, X3Dom is great. The latest incarnation of VRML and X3D, based on an ISO standard, durable, community supported, and championed by Fraunhofer, awesome. It still largely has that ‘VRML’ 90’s look, because of the standard lighting model. Fraunhofer has made great progress in putting forth the ‘Common Surface Shader’, and allows for any type of shading with the ‘Composed Shader’.
I’m making an X3D/X3Dom exporter for Unity, and that’s ‘shader heavy’. I figured I’d support most common stuff, and the ‘Common Surface Shader‘ handles a lot of that. For the rest, shaders need to be translated. That’s where the ‘Composed Shader‘ comes in. So I thought I’d start with a Terrain shader, because a) it’s a simple shader, just 4 textures plus 1 more to blend them, and b) terrain is a big flashy fun visual, so a little effort (in theory) for a lot of splash. Or, maybe just 2. And a fixed blend weight. Easy, right?
So of course I first turn to the documentation. God bless everyone who has put up any sort of documentation and examples and tutorials! Don’t get me wrong, these are good things. Except, in my case, I just needed a basic, simple texture blend shader to get started. Not a fun ‘gooch’ shader (no textures). The teapot example is pretty good, has more than I needed, but this is where I found the most gold.
I’ll cut to the chase. Here’s the simplest thing I could get working. Then vertex shader does almost nothing (as it shouldn’t) and the fragment shader just draws a texture blended with another one, nothing fancy. It may not be the best implementation, please let me know! I want to make it simple with a couple of textures:
<Material diffuseColor=".7 .7 .7" specularColor=".5 .5 .5" ></Material>
<ImageTexture url='"green.png"' ></ImageTexture>
<ImageTexture url='"rocks.png"' ></ImageTexture>
<TextureTransform scale='.1 .1' translation='.25 .33'></TextureTransform>
<TextureTransform scale='10 10' translation='.25 .33'></TextureTransform>
<field id='Picture0' name='Picture0' type='SFInt32' value='0' accessType='inputOutput'></field>
<field id='Picture1' name='Picture1' type='SFInt32' value='1' accessType='inputOutput'></field>
attribute vec3 position; // DO I EVEN NEED A VERTEX SHADER? I DON'T WANT TO ALTER VERTICES
attribute vec3 normal;
attribute vec2 texcoord;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec4 view_position;
varying vec2 fragTexCoord;
vec4 Pos = vec4(position.x, position.y, -position.z, 1.0); // USING THIS CUZ IT WORKS, NOT SURE IF REALLY NEEDED
fragTexCoord = vec2(texcoord.x, 1.0 - texcoord.y);
vec4 mvPosition = modelViewMatrix * Pos;
gl_Position = projectionMatrix * mvPosition;
precision highp float;
uniform sampler2D Picture0;
uniform sampler2D Picture1;
varying vec2 fragTexCoord;
vec4 texCol0 = texture2D(Picture0, fragTexCoord);
vec4 texCol1 = texture2D(Picture1, fragTexCoord);
gl_FragColor = texCol0 * .5 + texCol1 * .5; // Half this, half that
So why was that so hard?
1. x3dom.js is not tolerant of fools, but doesn’t say why. For example, I was getting this error:
Error: WebGL: A texture is going to be rendered as if it were black, as per the OpenGL ES 2.0.24 spec section 3.8.2, because it is a 2D texture, with a minification filter requiring a mipmap, and is not mipmap complete (as defined in section 3.7.10). x3dom-full.debug.js:3303
And no explanation of why. I checked the texture files, they are power-of-two and well behaved and work in other examples, why not mine?
Another example is that output from, for example, Vivaty Studio, has urls formatted technically correctly, but in such a way that x3dom will choke.
2. The documentation is incomplete. I know it’s not the top priority and it’s being done for the public good, not so much profit, but hey. I have no idea what is, and is not, needed, from the examples. I don’t need to futz with vertices, so do I even need that part? If so why? And what should it look like? What are attributes, uniforms, varying, and what exactly do they mean, and which part(s) are they valid in, and do they have to have a certain name or naming convention(s)?
3. X3Dom is buggy. ShaderPart url appears to not work.
So these are landmines. Here’s how to avoid them:
1. Make sure you are either using HTML conventions or XHTML conventions, not mixing them.
It would be nice if the x3dom parser would either tolerate this, or warn about it to let you know. But it’s simple to get into trouble using copy/paste from other sources, and get into trouble.
The first problem was caused by this:
That seems pretty correct, no? No! Because the rest of the page uses explicit end tags. Changing it to
fixed the problem. No problem with the texture, as the error code says. Problem with the X3DOM syntax.
Next, Vivaty Studio indents urls like this:
<ImageTexture url = ‘
Now, that white space is legal. But a one-liner in x3dom.js will fix that, just trim the url. Otherwise, the whitespace will be included in the url, leading to a bad url, and no texture load. And note the lack of end-tag. So be careful twice here.
2. Make sure you have exactly the same names listed in the fields and shader parts. And it seems there must be a Vertex shader, so if you have a ‘varying’ there, make sure it is also in your Fragment shader.
3. I had no luck using ‘ attribute vec2 texcoord;’ in the Fragment shader, so I put it in the Vertex shader and send it out as ‘varying vec2 fragTexCoord;’ from there, note that has to be included in the Fragment shader as such. And I don’t even know if this is correct, maybe I did something wrong and it’s not really needed, please correct me if I’m wrong.
4. MultiTextureTransforms are not yet implemented in X3Dom, so don’t rely on it. You can pass the scale and translation into your shader directly for the time being.
5. Unfortunately the ShaderPart url attribute appears to be broken. It will read the external file but it doesn’t use it properly. So you will have to ‘inline’ your shader code. Not ‘Inline’ like the node, ‘inline’ like paste the code in the file.
Problem with X3D/X3Dom spec
So based on XML, X3D/X3Dom should not have any dependencies on the order in which elements are listed within a container. But the MultiTexture (and associated coordinate and transforms) DO depend on order. This is bad. Fraunhofer seems to have begun to address this with the ‘value’ field on the Fields, but this is incomplete..
I suggest that a new attribute ‘index’ be allowed on ImageTexture and associated coordinate and transforms, to disambiguate this.
<ImageTexture index = ‘1’ url = ‘”funnyquotes.png”‘/>
<ImageTexture index = ‘0’ url = ‘”lovethat.png”‘/>
<TextureTransform index=’0′ scale=’1 2’/>
<TextureTransform index=’0′ scale=’.5 .2’/>
See where I’m going with that? Then the parser can match up these items by index. And they can be passed to the shader as such as well.