I have converted my Ocean simulation to use node materials. I think it is a vast improvement.
However, an important part of the simulation is an animated displacement map which I use for each grid and which should tile perfectly with adjoining grids. In my non-nodes version, there is no apparent seam between these grids. In the nodes version, there is a visible seam. Anyone know how to fix that?
Here is how the non-nodes version appears. Here is the nodes version. As you can see there is a visible seam straight ahead and straight behind.
The material definition I use for the non-node material is this:
grd_.Mt0[idx] = new MeshStandardMaterial({
color: grd_.Col,
map: grd_.Df0[idx],
metalness: 1.0, // 1 for max reflection
roughness: 0.7, // 0 for max reflection
roughnessMap: grd_.Rf0[idx],
normalMap: grd_.Nrm, // Animated normalMap
normalScale: new Vector2(4,4),
envMap: scene.background,
envMapIntensity: 4, // max reflection suggested = 5
premultipliedAlpha: true,
onBeforeCompile: shader => {
shader.uniforms.dmap = {value: grd_.Dsp}; // Displacement Map
shader.vertexShader = `
uniform sampler2D dmap;
${shader.vertexShader}
`
.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed += vec3(1.0,1.0,1.0)*(texture(dmap, uv).rgb * 1.0 + 0.0);
`
);
}
});
I have to use the onBeforeCompile section because the displacement map is a 3-dimensional map.
The material definition I use for the nodes version is this:
grd_.Mt0[idx] = new MeshStandardNodeMaterial({
colorNode: color(grd_.Col),
map: grd_.Df0[idx],
metalness: 0.5, // 1 for max reflection
roughness: 0.5, // 0 for max reflection
roughnessMap: grd_.Rf0[idx],
normalMap: grd_.Nrm, // Animated normalMap
positionNode: positionLocal.add(texture(grd_.Dsp)), // Displacement Map
envMap: scene.background,
envMapIntensity: 0.5,
});
FURTHER THOUGHTS
Having posted this, I am now wondering if the problem has to do with the old problem of trying to create Normal maps where, at the edges, you are trying to access displacement values that are “off the map”. I am using a shader to compute the normal values. But, because this the displacement map generates values in three dimensions (rather than just height), the shader has to use 5 points of reference, rather than 3. (At least, that is my understanding of what is happening.)
Consequently, at the edges, the shader is attempting to access values that are off the map. The good news is that, because the displacement values repeat, I should be able to use the values from the first (or last) row or column. I will try to work this out. But in the meantime, I wonder if there is a standard solution to this problem. Here the the shader code that I am using:
//= 5. Fragment Shader - Normal Map
// Note: The original program did not generate a "plug and play" Normal Map .
// Values had to be switched (at the bottom)
let ocean_normals = `
precision highp float; // GLSL3
precision highp int;
precision highp sampler2D;
in vec2 vUv;
out vec4 outColor;
uniform float u_grdres; // Segments in plane (256)
uniform float u_grdsiz; // Size of grid (1600)
uniform sampler2D u_displacementMap;
void main (void) {
float texel = 1.0/u_grdres;
float texelSize = u_grdsiz/u_grdres;
vec3 ctr = texture(u_displacementMap, vUv).rgb;
vec3 rgt = vec3(texelSize, 0.0, 0.0) + texture(u_displacementMap, vUv + vec2(texel, 0.0)).rgb - ctr;
vec3 lft = vec3(-texelSize, 0.0, 0.0) + texture(u_displacementMap, vUv + vec2(-texel, 0.0)).rgb - ctr;
vec3 top = vec3(0.0, 0.0, -texelSize) + texture(u_displacementMap, vUv + vec2(0.0, -texel)).rgb - ctr;
vec3 bot = vec3(0.0, 0.0, texelSize) + texture(u_displacementMap, vUv + vec2(0.0, texel)).rgb - ctr;
vec3 topRgt = cross(rgt, top);
vec3 topLft = cross(top, lft);
vec3 botLft = cross(lft, bot);
vec3 botRgt = cross(bot, rgt);
vec3 nrm3 = vec3(normalize(topRgt + topLft + botLft + botRgt));
vec3 tmp2 = nrm3;
nrm3.b = tmp2.g; // Switch b with g
nrm3.g = tmp2.b; // Siwtch g with b
outColor = vec4(nrm3*0.5+0.5, 1.0);
}
`;
Do I have to add some special handling if vUv.x or vUv.y => u_grdres? Or can I use some kind of modulo to force the index to wrap around? Or does it automatically wrap (to prevent errors)?