Triplanar and lighting

Hi,
I had success using triplanar mapping but there is one remaining bit I would like to clarify.

The mapping is correct texture wise but light doesn’t seem correct. It seems off or even not rendered on the surface.

So I read that post.
I am confused because it refers to shader chunks.

So, should I understand that to make a proper triplanar, I will have to add many shader chunks I don’t even understand (well sort of. The order is really unclear to me).

Then, using onBeforeCompile could be an idea but how to inject triplanar logic when the mesh material is already ill formed (no uvs).

aïe… things get complicated. What is the best beginner friendly way to make it happen?

Thanks for your patience!

It can be complicated to figure out how to change existing shaders by combing shader chunks. There are a few alternatives.

  • There is a built in Node system in Three.js that can be used to create custom shaders. I believe looking at the official examples is the best method to learn.
  • This library seems like a good approach for customising existing shaders. You can then just use your triplanar mapping code and pass the resulting color to the csm_DiffuseColor.
  • This shader library I have built myself which also has built in support for triplanar mapping. Here is how you could use it:
import { TextureLoader } from 'three';
import { attributes, float, NodeShaderMaterial, standardMaterial, triplanarMapping, uniformSampler2d, varyingVec3 } from 'three-shader-graph';

const texture = new TextureLoader().load('path/to/texture')

const sampler = uniformSampler2d('map')
const scale = float(1)

const textureColor = triplanarMapping(sampler, varyingVec3(attributes.normal), varyingVec3(attributes.position), scale)
const physicalColor = standardMaterial({color: textureColor.rgb()})

const material = new NodeShaderMaterial({
  color: physicalColor,
  uniforms: { map: { value: texture } }
})
material.uniforms.map.value = texture

Great Adam,

I will try your library tomorrow. :crossed_fingers: :smiley:

So,
I tried your library Adam, and i got an error: material.onBeforeRender is not a function.

So I kept going with the following snippet:

                        let geometry = new BoxGeometry(5, 5, 5);

                        let mtl = new Nodes.MeshStandardNodeMaterial();
                        let triplanarMapping = new Nodes.FunctionNode([
                                // Reference: https://github.com/keijiro/StandardTriplanar
                                'vec4 triplanarMapping( sampler2D map, vec3 normal, vec3 position, float scale ) {',

                                // Blending factor of triplanar mapping
                                '   vec3 bf = normalize( abs( normal ) );',
                                '   bf /= dot( bf, vec3( 1.0 ) );',

                                // Triplanar mapping
                                '   vec2 tx = position.yz * sscale;',
                                '   vec2 ty = position.zx * scale;',
                                '   vec2 tz = position.xy * scale;',

                                // Base color
                                '   vec4 cx = texture2D(map, tx) * bf.x;',
                                '   vec4 cy = texture2D(map, ty) * bf.y;',
                                '   vec4 cz = texture2D(map, tz) * bf.z;',

                                '   return cx + cy + cz;',

                                '}'
                        ].join('\n'));
                        
                        let scale = new Nodes.FloatNode(.02);

                        let diffuseM = new Nodes.TextureNode(await get_texture("c_diffu"));
                        let normalM = new Nodes.NormalMapNode(await get_texture("c_nm"))//new Nodes.NormalNode(Nodes.NormalNode.WORLD);
                        let posNode = new Nodes.PositionNode(Nodes.PositionNode.WORLD);

                        let triplanarMappingTexture = new Nodes.FunctionCallNode(triplanarMapping, {
                                map: diffuseM,
                                normal: new Vector3(4.0, 1.0, 8.5),
                                position: new Vector3(10.0, 1.0, 0.5),
                                scale: scale
                        });
                        //geometry.computeVertexNormals();

                        mtl.color.set( triplanarMappingTexture)

                        const cube = new Mesh(geometry, mtl);
                        cube.position.set(5, 1, 1)

                        console.log(geometry, cube)
                        scene.add(cube);

Unfortunately, while trying so many things I even lost the working triplanar piece of code. Back to point 0.
I even added a typo: sscale and it didn’t noticed the error, which makes me think that the code is never executed.
I tried different things the whole afternoon, I cannot take it anymore.

Could someone chime in and tell me why it doesn’t work please?

Hi

I am not sure why you are getting that error as it should not be coming from the library.

I uploaded a working demo of triplannar mapping with my library here:

Just run

npm install
npm start

and go to localhost:8080 in your browser to see it.

Thanks a lot Adam, I wonder if it is not related to the fact that I use three via a cdn instead of npm…

Something I need to investigate… :thinking: I will retry with your example right now :+1:

Ok, it is definitely the cdn approach hindering me!

Thanks a lot Adam :+1: :+1:

OK Adam, it worked!

Now to use your -great- library I will need to readjust the whole project; I will do it.

But for tinkering, what can I do to this code to make it work?

            let mtl = new Nodes.MeshStandardNodeMaterial();
            let triplanarMapping = new Nodes.FunctionNode([
                    // Reference: https://github.com/keijiro/StandardTriplanar
                    'vec4 triplanar_mapping( sampler2D map, vec3 normal, vec3 position, float scale ) {',

                    'scale = 1;',
                    // Blending factor of triplanar mapping
                    '   vec3 bf = normalize( abs( normal ) );',
                    '   bf /= dot( bf, vec3( 1.0 ) );',

                    // Triplanar mapping
                    '   vec2 tx = position.yz * scale;',
                    '   vec2 ty = position.zx * scale;',
                    '   vec2 tz = position.xy * scale;',

                    // Base color
                    '   vec4 cx = texture2D(map, tx) * bf.x;',
                    '   vec4 cy = texture2D(map, ty) * bf.y;',
                    '   vec4 cz = texture2D(map, tz) * bf.z;',

                    '   return cx + cy + cz;',

                    '}'
            ].join('\n'));

            let scale = new Nodes.FloatNode(.02);

            let nn = new Nodes.TextureNode(await get_texture("c_bump"));
            let rr = new Nodes.NormalNode(Nodes.NormalNode.WORLD);
            let pp = new Nodes.PositionNode(Nodes.PositionNode.WORLD);

            var triplanarMappingTexture = new Nodes.FunctionCallNode(triplanarMapping, {
                    map: nn,
                    normal: rr,
                    position: pp,
                    scale: scale
            });


            mtl.color.set(triplanarMappingTexture);

I am not sure where I got it wrong in the process. but my shape is appearing without texture.

There are a couple of problems.

  • scale = 1; does not work as it mixes integers with floats. This is an annoying part of GLSL and I make the mistake often myself but if you just write 1 instead of 1.0, it gets interpreteted as an integer and it fails if you try to use it as a float.
  • Nodes.FunctionCallNode seems to take an array of nodes instead of an object at least in the version of the library I used now.

I added a working example using Nodes here in a separate branch. I set the metalness and roughness to get it closer to looking to my example.

The implementation I had in my library of triplanar mapping was missing this part you had in yours. bf /= dot( bf, vec3( 1.0 ) );' so it looks much brighter. Until I have updated my library, you can use this code if you want to get it to behave lie yours.

function triplanarMapping(texture: Sampler2DNode, normal: Vec3Node, position: Vec3Node, scale: FloatNode): RgbaNode {
  // Blending factor of triplanar mapping
  const bf0 = normalize(abs(normal))
  const bf = bf0.divideScalar(dot(bf0, vec3(1,1,1)))

  // Triplanar mapping
  const tx = position.yz().multiplyScalar(scale)
  const ty = position.zx().multiplyScalar(scale)
  const tz = position.xy().multiplyScalar(scale)

  // Base color
  const cx = texture.sample(tx).multiplyScalar(bf.x())
  const cy = texture.sample(ty).multiplyScalar(bf.y())
  const cz = texture.sample(tz).multiplyScalar(bf.z())

  return cx.add(cy).add(cz).rgba()
}

Yes indeed, it is much brighter now. Look good :wink:

Ok, so after completely changing the strategy: here is a problem I already had with the CDN approach which makes me think I will soon be crazy.

But first, here is the html I compressed as much as I could to help to pinpoint the issue I have:
minimum.html (5.7 KB)

this file is the minimum I could get away with, so no controls except mouse.

The culprit is when you look toward the ground, where the mesh is. Once it is meant to enter the screen: crash.

I run Linux, Debian.
I have an up to date npm and node.

  • did npm -i --save-dev three
  • did npm -i three-shader-graph
  • That file is prepared by Parcel. The public files go to /dist. the html is in the current folder.
  • I run the command, in my folder: npx parcel minimum.html
  • and then go to http://localhost:1234/.

Here is a screenshot of the error I get:

material.onBeforeRender is not a function.

Now, Apparently it is not happy with multiple instances of Threejs.
I don’t know how to solve this.

Second, the issue only happen with Nodes. When using normal materials, everything is fine.

So what is the issue, finally? Please help me :pray: :pray: :pray:

I found the issue now. I published a new version of the library with the fix. If you run npm install --save three-shader-graph@0.1.11 it should hopefully fix it.

The problem was that I had put three as a dependency instead of a peer dependency. This lead to having multiple instances of three. This update was recently made to three which introduced a new method on the Material class which is called when rendering. As the NodeShaderMaterial class in my library was extending from a class in a different instance of the three code, it did not have this new method which leads to the error of material.onBeforeRender not being a function.

I am very sorry that this happened. If you have any more issues with the library, you are welcome to message me directly or to create an issue on the repo’s in GitHub.

Adam, you rock,

The texture is in my scene now, no more bugs.

yes yes YES :+1: :+1: :+1:

BTW, I was thinking about a small improvement on the formula. I will work on it and create an issue on your Github once done.

Again, thanks a lot :slight_smile: