Drawing on multiple layers

I am pursuing an effect where the user can draw with multiple colors and use multiple layers, as extension of this project:

I have already tried to allocate three colors to three different RenderTextures.
The biggest problem seem to lay within the .colorBuffer subclass, which overwrites the renderTarget with the new chosen color at each brush-click?

(tried also to use the

var canvas = document.createElement("canvas")

We have this example - which uses single render target but multiple colors.
http://playcanvas.github.io/#graphics/painter.html

What are you trying to use the multiple layers and render targets for? Could you please describe the goal of it?

Yes, I will be glad to (btw: there might be some ‘chicken-and-egg’ effect within this post … we touched upon the same ‘general issue’ of mine at the ‘playcanvas engine’ github issues some days ago https://github.com/playcanvas/engine/issues/2556)

But yes I am still trying to make drawing effects upon; models (quite more complex than simple primitives).
In a sub-project, to a much larger one, I am trying to enable the user to draw on gaming-quality characters (for tattoo artists) as well as high-end modellings of real cars (comercial car stickers).

For now I am focusing on the ‘tattoo part’. In order to work with multiple layers and maybe an alpha (within my first approach), I am expanding one of the scripts (from How to get the uv coordinate of a mesh) like this:


var RenderLayerTotexturePnt = pc.createScript('renderLayerTotexturePnt');

RenderLayerTotexturePnt.attributes.add("layerName", {type: "string"});

// initialize code called once per entity
RenderLayerTotexturePnt.prototype.initialize = function() {
    // Create a 512x512x24-bit render target with a depth buffer
    var colorBuffer = new pc.Texture(this.app.graphicsDevice, {
        width: 512,
        height: 512,
        format: pc.PIXELFORMAT_R8_G8_B8_A8,
        autoMipmap: true
    });
    colorBuffer.minFilter = pc.FILTER_LINEAR;
    colorBuffer.magFilter = pc.FILTER_LINEAR;
    var renderTarget = new pc.RenderTarget(this.app.graphicsDevice, colorBuffer, { 
        depth: true
    });
    // New part that I maybe will elaborate upon with clearStencilBuffer and/or cullingMask (but dont know how?)
    this.app.root.findByName("RenderCamera").camera.clearColorBuffer =true; // or 'false'?
    var color = this.app.root.findByName("RenderCamera").camera.clearColor;
    color.r = 0.59;
    this.app.root.findByName("RenderCamera").camera.clearColor = color;
    // --- end of new part
    
    var layer = this.app.scene.layers.getLayerByName(this.layerName);
    layer.renderTarget = renderTarget;
};

It lets me go a bit of the way (colorBuffer-wise), but only gets me from

  • to

Likewise with black color-brush (layer):

  • to

PS: yes @Leonidas showed me the cube-with-lines example as well.
I need a tool-in-my-mindbox to use it though - the HTML inline format puzzles me?
How to, for instance, ‘convert’:

app.on(“update”, function (dt) {

to

jsScript.prototype.update = function() {

and so on? (don’t get me wrong, I do know how to convert some of it … but far from all)

{

 // disable brushes from the previous frame and return them to the free pool
        while (usedBrushes.length > 0) {
            var brush = usedBrushes.pop();
            brush.enabled = false;
            brushes.push(brush);
        }

seems quite far from examples like:

    for (var i=0;i<this.entity.model.model.meshInstances.length;i++)
    { 
        var material = this.entity.model.model.meshInstances[i].material;
        console.log(layer.renderTarget.colorBuffer);
        material.emissiveMap = layer.renderTarget.colorBuffer;
        material.update();    
    } 

and

var layer = this.app.scene.layers.getLayerByName(this.layerName);  
this.material.setParameter("texture_mask",layer.renderTarget.colorBuffer);

this.app.root.findByName("UVImage").element.texture = layer.renderTarget.colorBuffer;

}


Bottom-bottom line; I can tell that this chunk below (from https://github.com/playcanvas/playcanvas.github.io/blob/master/graphics/painter.html) and paintLayer.id are vital to solving my problem:

        var paintCamera = new pc.Entity();
        paintCamera.addComponent("camera", {
            clearColorBuffer: false,
            projection: pc.PROJECTION_ORTHOGRAPHIC,
            layers: [paintLayer.id]
        });

->> so something like:

var paintLayer = new pc.Layer({ name: "paintLayer" });
        app.scene.layers.insert(paintLayer, 0);

        // set up layer to render to the render targer
        paintLayer.renderTarget = renderTarget;

        // we render multiple brush imprints each frame to make smooth lines, and set up pool to reuse them each frame
        var brushes = [];
        function getBrush() {

                brush = createPrimitive("sphere", new pc.Vec3(2, 1, 0), new pc.Vec3(1, 1, 1), [paintLayer.id], brushMaterial);
  • mixed with the Brush.js script?:
var Brush = pc.createScript('brush');
Brush.attributes.add('cameraEntity', {type: 'entity'});
Brush.attributes.add('brushSize', {type: 'number',min: 1, max: 50});

// initialize code called once per entity
Brush.prototype.initialize = function() {
    var self = this;
    var height = this.cameraEntity.camera.orthoHeight;
    var math = pc.math;

    var size = this.brushSize / 100;
    this.entity.setLocalScale(size,size,size);
    
    this.chosenColor = 0; /*0: RED; 1: GREEN; 2: BLACK*/

    this.on("attr:brushSize",function() {
        var size = this.brushSize / 100;
        this.entity.setLocalScale(size,size,size);
    });

    this.app.on("hitUV",function(hitPos) {
        if(!this.entity.model.enabled) this.entity.model.enabled = true;
        this.entity.setLocalPosition(math.lerp(-height, height, hitPos.x), math.lerp(-height, height, hitPos.y), -3);
    },this);

    this.app.root.findByName("Reset").element.on("click",function() {
        this.cameraEntity.camera.clearColorBuffer = true;
        setTimeout(function() {
            self.cameraEntity.camera.clearColorBuffer = false;
        },50);
    },this);

    var brushs = this.app.root.findByName("Brush Group").children;
    brushs[0].element.on("click",function() {
        this.canvas1Layer = document.createElement("canvas"); this.canvas1Layer = self.cameraEntity.camera.colorBuffer;
        this.brushSize = 3;
        for(var i = 0;i < brushs.length;brushs[i++].element.color = pc.Color.WHITE);
        brushs[0].element.color = pc.Color.RED; 
        this.chosenColor =0;
    },this);
    brushs[1].element.on("click",function() {
        this.brushSize = 7;
        for(var i = 0;i < brushs.length;brushs[i++].element.color = pc.Color.WHITE);
        brushs[1].element.color = pc.Color.GREEN;
        this.chosenColor =1;
    },this);
    brushs[2].element.on("click",function() {
        this.brushSize = 16;
        for(var i = 0;i < brushs.length;brushs[i++].element.color = pc.Color.WHITE);
        brushs[2].element.color = pc.Color.BLACK ;this.chosenColor =2;
    },this);
    if(this.chosenColor===0){brushs[0].element.color = pc.Color.RED;}
    if(this.chosenColor===1){brushs[1].element.color = pc.Color.GREEN;}
    if(this.chosenColor===2){brushs[2].element.color = pc.Color.BLACK;}
    //brushs[0].element.color = pc.Color.BLUE;

    this.app.on("brushUp",function() {
        this.entity.model.enabled = false;        
    },this);
};
1 Like

Not sure if you gave it a try, but I did myself. From the inner workings of the " How to get the uv coordinate of a mesh" example, I am focusing on these scripts:

renderLayerTotexture.js
and
brush.js

I write in much of your Lines-on-Cube example in renderLayerTotexture.js like this:

// initialize code called once per entity
RenderLayerTotexturePnt.prototype.initialize = function() {
    // Create a 512x512x24-bit render target with a depth buffer
    var colorBuffer = new pc.Texture(this.app.graphicsDevice, {
        width: 512,
        height: 512,
        format: pc.PIXELFORMAT_R8_G8_B8_A8,
        autoMipmap: false
    });
    colorBuffer.minFilter = pc.FILTER_LINEAR;
    colorBuffer.magFilter = pc.FILTER_LINEAR;

    this.renderTarget = new pc.RenderTarget(this.app.graphicsDevice, colorBuffer, {         depth: false    });
    
    // create a layer for rendering to texture, and add it to the beginning of layers to render into it first
    
        this.paintLayer = new pc.Layer({ name: "paintLayer" });
        this.fromExistingLayersID_toNewOnes = 20;
        this.app.scene.layers.insert(this.paintLayer, this.fromExistingLayersID_toNewOnes);
    
              /// paintlayer to RenderTarget:         
              var paintCamera = this.app.root.findByName("RenderCamera");                                    
                                    paintCamera.addComponent("camera", {
                                    // clearColorBuffer: false, // already as such in editor
                                    projection: pc.PROJECTION_ORTHOGRAPHIC,
                                    layers: [this.paintLayer.id]
                                });      
    
  var layer = this.app.scene.layers.getLayerByName(this.layerName);
    layer.renderTarget = this.renderTarget;
};

RenderLayerTotexturePnt.prototype.newPaintLayer = function(){
// henter fra foerste deklaration i raycastPnt:   
this.fromExistingLayersID_toNewOnes++;
this.app.scene.layers.insert(this.paintLayer, this.fromExistingLayersID_toNewOnes); 
// set up layer to render to the render targer
this.paintLayer.renderTarget = this.renderTarget;

};

In brush.js I call the newPaintLayer function each time a brush is pressed:

...
    // Create new paintlayer at renderLayerToTexture:
    var newPntLayerFunction_Ent = this.app.root.findByName("Root"); var newPntLayerFunction_Scr = newPntLayerFunction_Ent.script.renderLayerTotexturePnt; 
    
    var brushs = this.app.root.findByName("Brush Group").children;
    brushs[0].element.on("click",function() {
        this.brushSize = 3;
        for(var i = 0;i < brushs.length;brushs[i++].element.color = pc.Color.WHITE);
        brushs[0].element.color = pc.Color.RED; 
        this.chosenColor =0;newPntLayerFunction_Scr.newPaintLayer();
    },this);

Unfortunately it now outputs me the error of:

Trying to bind current color buffer as a texture

Scripts run without errors, but the all of the ‘back’-material (paint included) still change color at brush-shifts. Ran out of debug-options.

Fortunately (for my own sake) I have now rediscovered an external JS-technique I went by, some months ago, that helps me realize the effect.
Not being able to implement it in PlayCanvas (at least right away) can only make me set this as [SEMI-SOLVED].

I hope to hear from other texture and colorBuffer-experts (cf @mvaligursky, @Leonidas[£], @yaustar), over time, so we can find the perfect mix between:

… for now the progress stopped somewhere within:
several ‘new pc.Layer({ name: “paintLayer” });’ assigned to one ‘rendertexture’ only (from starting point at least).

[£] As one of the best references that you/Leo, showed me (webfundamentals.org), refers to buffers like in OpenGL (where colorbuffer === gl_buffer etc.), a tutorial on how to use layers, textures, rendertargets, camera-components and colorbuffers seems strongly needed [the colorbuffer PC-class is too solitary in its overall websource presence, compared to all the OpenGL, GLSL etc.].

This error is due to your code trying to render the object that user the render target texture on material, into the render target itself, which webgl (and most other rendering APIs) does not support. That mesh needs to be excluded from rendering into render target by no placing it on that layer.

2 Likes

Ok, thanks. I might return to the subject with that in mind - I also hope that the texture handling will get better overall within PlayCanvas (once again PC is a bit behind Three.js, that allows SVG as texture like within this example: http://jsfiddle.net/L4pepnj7/).
The options within PC are already great, but can become better :slight_smile: