StandardMaterial shader/multiple materials to mesh

Hi there, I’m looking for a way to texture out a mesh while keeping all the lightings/shadows and all that good stuff rendered, is there any examples regarding StandardMaterial implementation in the shader?

I’d have to use several textures by height, I have a working example that uses default shader Material, but lack of knowledge stops me from getting further


Hi @Newbie_Coder,

What you are looking for is using shader chunks for authoring your custom shaders, so they work with PlayCanvas lighting/shadows.

Start with overriding the diffuse shader chunk.

Check the last two examples here on how to get started with that: Tutorials | Learn PlayCanvas

Hi there @Leonidas, I have checked all the shader examples, seems like last engine update hit them aswell, none of those are working, currently I’m playing with materials in the editor just to learn more and well I have a question, what makes material to change it’s rendering direction as shown in this pic:

The answer is UV channels

Would it be possible to force the render of material from bottom to top instead from side to side on the generated mesh?

Mesh has single UV channel

Okay, I’ve written a simple shader

attribute vec3 aPosition;
attribute vec2 aUv0;
attribute vec3 aNormal;
varying float height;
uniform mat4 matrix_model;
uniform mat4 matrix_viewProjection;
varying vec3 vWorldPos;
varying vec4 vProjectedPos;
void main() 
    height = aPosition.y;
    gl_Position = matrix_viewProjection * matrix_model * vec4( aPosition, 1.0 );


varying vec3 vWorldPos;
varying vec4 vProjectedPos;
varying float height;
uniform sampler2D sandTexture;
uniform sampler2D grassTexture;

void main(void)
    vec2 uv2 = vWorldPos.xz;
    vec4 sand = (step(0.0,  height) - step(0.2,  height)) * texture2D( sandTexture, uv2);
    vec4 grass = (step(0.2,  height) - step(3.0,  height)) * texture2D( grassTexture, uv2);
    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0) + sand + grass;
// initialize code called once per entity
Water.prototype.initialize = function() {

    var app =;
    var gd = app.graphicsDevice;

    var shaderDefinition = {
        attributes: {
            aPosition: pc.SEMANTIC_POSITION,
            aUv0: pc.SEMANTIC_TEXCOORD0,
            aNormal: pc.SEMANTIC_NORMAL
        vshader: this.vertexShader.resource,
        fshader: "precision " + gd.precision + " float;\n\n" + this.fragmentShader.resource,

    this.shader = new pc.Shader(gd, shaderDefinition);

    var material = new pc.Material();
    material.setParameter('sandTexture', this.sandTexture.resource);
    material.setParameter('grassTexture', this.grassTexture.resource);       

    this.shaderLoaded = true;
    this.material = material;
    this.entity.render.meshInstances[0].material = this.material;

I honestly have no idea how to pass shader chunks, after console logging this.material I can’t even see chunks property

Another problem: vec2 uv2 = vWorldPos.xz; multiplying it should change the scale of textures but it doesn’t, why is that?


Hi, I’m trying to convert a basic shader to Albedo one, I have several questions:

  1. What is equivalent to a Position.y?
  2. vWorldPos.xz = to?
  3. Can Albedo be a Vec4?

If you are using shader chunks the following attribute is available in your code for the world position vPositionW.

No albedo is a Vec3. But the question is why do you need a Vec4 for color? Do you need it for transparency?

If that’s the case you just need to override the opacity shader chunk and set the dAlpha value to your desired pixel transparency.

1 Like

I understand that shader chunks can be tricky to get started given it’s an unofficial and mostly undocumented feature.

I may spent some time tomorrow to setup a simple pixel shader using shader chunks on the default PlayCanvas terrain example, that may be of help to you.

1 Like

I think I am near the results but I cannot figure out replacement for aPosition.y (SEMANTIC_POSITION)
FRAGMENT varying height does not match any VERTEX varying

You can always override a vertex shader chunk (e.g. base or start) and pass your own varyings to your pixel shaders.


        vec2 uv2 = vec2(vUv0.x * 15.0, vUv0.y * 15.0);
    vec4 sand = (step(0.0, height) - step(0.1, height)) * texture2D( uTexTwo, uv2);


vec4 getPosition(){
    vec2 uvCalc = vUv0;
    vec3 pos = vertex_position;
    vec3 height = vertex_position.y;
    dPositionW = (matrix_model * vec4(vertex_position, 1.0)).xyz;
    return matrix_viewProjection * matrix_model * vec4(pos, 1.0);


I’m using a varying float height
Vertex_position doesn’t work either while aPosition.y was the solution before Albedo

Or it won’t work that way?

What about a simple example with smoothstep that Mixes albedo with 2 textures instead/no height

Still trying to figure out how to pass aPosition to Albedo (I’m overwriting diffusePS), what about using a custom shader for texturing mesh (glFragColor) but also adding, albedo transperent texture over it

I’ll try and create an example later today on how to do that using shader chunks.

Hi @Newbie_Coder,

Here is an example of a terrain smoothstep shader that mixes two textures based on the terrain elevation (height). The shader works by overriding the diffusePS shader chunk.

Posting both the full script code and an example public project, hope that helps!

var TerrainShader = pc.createScript('terrainShader');

TerrainShader.attributes.add('material', {
    type: 'asset',
    assetType: 'material'

TerrainShader.attributes.add('texture1', {
    type: 'asset',
    assetType: 'texture'

TerrainShader.attributes.add('texture2', {
    type: 'asset',
    assetType: 'texture'

TerrainShader.attributes.add('minElevation', {
    type: 'number',
    default: 0

TerrainShader.attributes.add('maxElevation', {
    type: 'number',
    default: 5

TerrainShader.attributes.add('stepElevation', {
    type: 'number',
    default: 0.5,
    min: 0,
    max: 1

TerrainShader.attributes.add('texBorder', {
    type: 'number',
    default: 0.03

// initialize code called once per entity
TerrainShader.prototype.initialize = function () {

    const material = this.material.resource;

    material.chunks.diffusePS = `
uniform sampler2D texture1;
uniform sampler2D texture2;

uniform float minElevation;
uniform float maxElevation;
uniform float stepElevation;
uniform float texBorder;

uniform vec3 material_diffuse;

void getAlbedo() {
    dAlbedo = vec3(1.0);

    dAlbedo *= material_diffuse.rgb;

    vec3 tex1 = gammaCorrectInput(texture2D(texture1, $UV, textureBias).rgb);
    vec3 tex2 = gammaCorrectInput(texture2D(texture2, $UV, textureBias).rgb);

    float elevationRange = maxElevation - minElevation;
    float elevationFactor = clamp((vPositionW.y - minElevation) / elevationRange, 0.0, 1.0);

    vec3 tex1Color = smoothstep(0.0, stepElevation + texBorder, elevationFactor) * tex1;
    vec3 tex2Color = smoothstep(stepElevation - texBorder, 1.0, elevationFactor) * tex2;

    dAlbedo = tex1Color + tex2Color;

    dAlbedo *= gammaCorrectInput(saturate(vVertexColor.$VC));


    // --- events
    this.on('attr', this.setAttributes);

TerrainShader.prototype.setAttributes = function () {

    const material = this.material.resource;

    material.setParameter('texture1', this.texture1.resource);
    material.setParameter('texture2', this.texture2.resource);

    material.setParameter('minElevation', this.minElevation);
    material.setParameter('maxElevation', this.maxElevation);
    material.setParameter('stepElevation', this.stepElevation);
    material.setParameter('texBorder', this.texBorder);


Such a smart and clean code, many thanks! This is definitely going to be useful for the whole PlayCanvas community :nerd_face:

1 Like

Pretty much understood the code except this part, what does vVertexColor and VC stand for?

That’s part of the default PlayCanvas diffuse shader chunk and it’s responsible for rendering the vertex colors of the model, if available and selected in the material.

It’s not really used in this sample, but I’ve kept it to keep the shader compatible.

1 Like