[SOLVED] Custom Shader (Fresnel)

Hello everyone,
I tried to create a simple custom fresnel shader. However, I receive this error:

The implementation mostly derivated from https://github.com/poikilos/KivyGlops/blob/master/shaders/fresnel.glsl
and in parts from Add Bloom to model?

Besides the error mentioned above, I am not quite sure where to get the camera’s world position (or if I even need it) - my understanding of shader code is severly lacking. If anyone could provide me with a meaningful documentation on how GLSL works in Playcanvas, it would be much appreciated.

Here’s a link to a demo project: https://playcanvas.com/editor/scene/1082597

This is the vert code:

// --- VERTEX SHADER ---

attribute vec3 aPosition;
attribute vec3 aNormal;

uniform mat4 matrix_model;
uniform mat4 matrix_viewProjection;

varying vec3 vNormal;
varying vec4 vPos;

void main(void)
{
    vec4 pos = matrix_model * vec4(aPosition, 1.0);
    vPos = matrix_viewProjection * pos;
    gl_Position = vPos;
    vNormal = aNormal.xyz;
}

and the frag code:

// --- FRAGMENT SHADER ---

precision mediump float;

uniform float uPower;    // Fresnel Power
uniform vec4 uColor;     // Fresnel Color

varying vec3 vNormal;
varying vec4 vPos;
uniform vec3 camera_worldPos;   // where do I get this from??

void main(void) {
        
    vec3 V = normalize(camera_worldPos.xyz - vPos.xyz);
    vec3 N = normalize(vNormal);
    
    float fresnel = pow( 1.0- dot(N, V), uPower);
    vec4 fresnel_color = vec4(fresnel, fresnel, fresnel, 1.0);
        
    // Output to screen
    gl_FragColor = uColor * fresnel_color;    
}

and the js:

var fresnel = pc.createScript('fresnel');


fresnel.attributes.add('frag', { type: 'asset', assetType: 'shader', description: "Fragment shader"});
fresnel.attributes.add('vert', { type: 'asset', assetType: 'shader', description: "Vertex shader"});

fresnel.attributes.add('power', {type: 'number', default: 3.0, description: 'Fresnel Power'});
fresnel.attributes.add('color', {type: 'rgba', description: 'Fresnel Color'});


fresnel.prototype.initialize = function() {
    var gd = this.app.graphicsDevice;

    var vertexShader = this.vert.resource;
    
    var fragmentShader = "precision " + gd.precision + " float;\n";
    fragmentShader = fragmentShader + this.frag.resource;

    var shaderDefinition = {
        attributes: {
            aPosition: pc.SEMANTIC_POSITION,
            aNormal: pc.SEMANTIC_NORMAL
        },
        vshader: vertexShader,
        fshader: fragmentShader
    };
    
    this.shader = new pc.Shader(gd, shaderDefinition);
    this.material = new pc.Material();
    this.entity.model.model.meshInstances[0].material = this.material;
    this.material.shader = this.shader;
    
    // set material parameters    
    this.material.setParameter('uPower', this.power);
    this.material.setParameter('uColor', this.color);
    
    this.material.update();
};

It’s what you’re asking, but you could perhaps use this solution:

@mvaligursky

I’ve already stumbled on that post as well, but haven’t considered using it yet. Main reason for that being that this solution
A. seems a little performance heavy compared to a simple vert/frag shader with nothing fancy (or maybe it isn’t)
B. needs an extra sperical gradient texture which I would have to manually adjust externally if I want to adjust the Fresnel Power

What is your opinion on the Shader Chunks implementation discussed in this thread:
How to create simple fresnel shader in emissive ?
Which one is the “intended” or more desirable solution in this case?

Edit: And how would one go about driving the Opacity of the material by the Fresnel? (e.g. making the center of a sphere less transparent while the rim is opaque-ish)

I think the problem is that you are passing instance of Color class here, but need to pass an array of 4 values … so do a conversion when you pass it.

instead of
this.material.setParameter('uColor', this.color);
try
this.material.setParameter('uColor', [this.color.r, this.color.g, this.color.b, this.color.a]);

As you mention, you also need to pass in the camera world position … it’d be something like this

var cameraPos = myCameraEntity.getPosition();
material.setParameter('camera_worldPos', [cameraPos.x, cameraPos.y, cameraPos.z]);

@mvaligursky

I think the problem is that you are passing instance of Color class here, but need to pass an array of 4 values … so do a conversion when you pass it.

This did resolve the issue error, thank you.
Now for the View Direction: Is there a way (besides calculating it from vertex position and camera coordinates) how to get the view direction from the active camera?
Let’s, for the moment, assume I have multiple cameras and want to switch between them and don’t want to call var cameraPos = myCameraEntity.getPosition(); every frame

Looking at the code, it seems you can use the built in uniform “view_position” to get camera position in the shader

I updated this engine example to use it and it works

1 Like

Ok, so now it sort of works…

But as you can see, the fresnel is somewhat squashed on the vertical axis AND it only works from one camera angle (0,0,0) - as soon as the camera moves, the fresnel stays behind.

I updated the Demo project with the current code:
https://playcanvas.com/editor/scene/1082597

Honestly, I’m at a loss here… I suspect that I misstyped / ill-copied a portion of the actual fresnel calculation, but I cannot figure out exactly what’s wrong :confused:

maybe you use camera position as a camera forward direction?
you might need to subtract it from vertex world position to get forward direction.
See how that point cloud simulation example does it.
But this is just a guess … I have not looked at your code, I’m busy at the moment.

1 Like

@mvaligursky

I’m already doing it like it is done in the point cloud simulation.
Don’t feel preasured by my repeated posting, but I’d be thankful if you could take a look at it as soon as you’re free again :wink:

Ok, nevermind, I fixed it!
(The error lay with my calculation - I used the vertex position in view space whereas world space was required for it to work)

Here’s the demo if anyone is interested:
https://playcanvas.com/editor/scene/1082597

This shader supports:

  • Fresnel with Bias, Power and Scale attributes
  • Two Colors (inner and outer)
  • Transparency (use ColorA or ColorB alpha value)

Here’s the Vertex Shader:

// --- VERTEX SHADER ---

attribute vec3 aPosition;
attribute vec3 aNormal;

uniform mat4 matrix_model;
uniform mat4 matrix_viewProjection;

varying vec3 vNormal;
varying vec4 vPos;

void main(void)
{
    vPos = matrix_model * vec4(aPosition, 1.0);     // vertex position (world space)
    gl_Position = matrix_viewProjection * vPos; 
    
    vNormal = aNormal.xyz;
}

…the Fragment Shader:

// --- FRAGMENT SHADER ---

precision mediump float;

uniform float uBias;    // Fresnel Bias
uniform float uPower;   // Fresnel Power
uniform float uScale;   // Fresnel Scale

uniform vec4 uColorA;   // Fresnel Color A (inner color)
uniform vec4 uColorB;   // Fresnel Color B (outer color)

varying vec3 vNormal;   // normal (per Vertex)
varying vec4 vPos;      // position (per Vertex)

uniform vec3 view_position;     // camera position (world space)

void main(void) {
    // implementation: http://kylehalladay.com/blog/tutorial/2014/02/18/Fresnel-Shaders-From-The-Ground-Up.html

    vec3 V = normalize(vPos.xyz - view_position.xyz);    
    vec3 N = normalize(vNormal);
    
    float fresnel = uBias + uScale * pow(1.0 + dot(V, N), uPower);
        
    vec4 col = mix(uColorA, uColorB, fresnel);    
    
    // Output to screen
    gl_FragColor =  col; 
}

… and the Javascript snipet:

var fresnel = pc.createScript('fresnel');


fresnel.attributes.add('frag', { type: 'asset', assetType: 'shader', description: "Fragment shader"});
fresnel.attributes.add('vert', { type: 'asset', assetType: 'shader', description: "Vertex shader"});

fresnel.attributes.add('bias', {type: 'number', default: 0.0, description: 'Fresnel Bias'});
fresnel.attributes.add('power', {type: 'number', default: 3.0, description: 'Fresnel Power'});
fresnel.attributes.add('scale', {type: 'number', default: 1.0, description: 'Fresnel Scale'});

fresnel.attributes.add('colorA', {type: 'rgba', description: 'Fresnel Color A (inner color)'});
fresnel.attributes.add('colorB', {type: 'rgba', description: 'Fresnel Color B (outer color)'});


fresnel.prototype.initialize = function() {
    var gd = this.app.graphicsDevice;

    var vertexShader = this.vert.resource;
    
    var fragmentShader = "precision " + gd.precision + " float;\n";
    fragmentShader = fragmentShader + this.frag.resource;

    var shaderDefinition = {
        attributes: {
            aPosition: pc.SEMANTIC_POSITION,
            aNormal: pc.SEMANTIC_NORMAL
        },
        vshader: vertexShader,
        fshader: fragmentShader
    };
    
    this.shader = new pc.Shader(gd, shaderDefinition);
    this.material = new pc.Material();    
    this.material.blendType = pc.BLEND_PREMULTIPLIED;
    this.entity.model.model.meshInstances[0].material = this.material;
    this.material.shader = this.shader;
    
    // set material parameters    
    this.material.setParameter('uBias', this.bias);
    this.material.setParameter('uPower', this.power);
    this.material.setParameter('uScale', this.scale);
    
    this.material.setParameter('uColorA', [this.colorA.r, this.colorA.g, this.colorA.b, this.colorA.a]);
    this.material.setParameter('uColorB', [this.colorB.r, this.colorB.g, this.colorB.b, this.colorB.a]);

    this.material.update();
};

@mvaligursky Could you please flag this topic as [SOLVED]?

6 Likes

A post was split to a new topic: Fresnel shader with world normals