Applying glow effect to a square using glsl without texture
Asked Answered
F

1

6

I've taken some same code from Android OpenGL tutorials, and I'm wondering is it possible to achieve the glowing effect seen here:

http://glslsandbox.com/e#25224.0

using the Square implementation below? i.e without using textures? I'd like to apply this glowing effect to the entire Square i.e fill

The link above uses a resolution variable, im not sure if this would be needed if I was trying to place effect on my shape. I'm assuming the time variable would not be needed?

I've seen many examples online off fragment shaders being used to produce a glow effect but most of them use textures.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import android.opengl.GLES20;

/**
 * A two-dimensional square for use as a drawn object in OpenGL ES 2.0.
 */
public class Square {

    private final String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = uMVPMatrix * vPosition;" +
            "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}";

    private final FloatBuffer vertexBuffer;
    private final ShortBuffer drawListBuffer;
    private final int mProgram;
    private int mPositionHandle;
    private int mColorHandle;
    private int mMVPMatrixHandle;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

    private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
    float color[] = { 0.2f, 0.709803922f, 0.898039216f, 1.0f };

    /**
     * Sets up the drawing object data for use in an OpenGL ES context.
     */
    public Square() {

        ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

        // prepare shaders and OpenGL program
        int vertexShader = MyGLRenderer.loadShader(
                GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(
                GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        mProgram = GLES20.glCreateProgram();             // create empty OpenGL Program
        GLES20.glAttachShader(mProgram, vertexShader);   // add the vertex shader to program
        GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
        GLES20.glLinkProgram(mProgram);                  // create OpenGL program executables
    }

    /**
     * Encapsulates the OpenGL ES instructions for drawing this shape.
     *
     * @param mvpMatrix - The Model View Project matrix in which to draw
     * this shape.
     */
    public void draw(float[] mvpMatrix) {

        GLES20.glUseProgram(mProgram);

        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        GLES20.glVertexAttribPointer(
                mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        MyGLRenderer.checkGlError("glGetUniformLocation");

        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
        MyGLRenderer.checkGlError("glUniformMatrix4fv");

        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}
Floccule answered 17/5, 2015 at 0:21 Comment(0)
M
6

The only reason why resolution variable is used is only to get a valid uv mapping. Generally, I would recommend you to add texture coordinates (uv mapping) to your Square. You will not have to use textures, only texture coordinates. In this case your fragment shader would be:

uniform float u_time;
varying vec2 v_uv;

void main( void ) {
    vec2 uv = v_uv;
    // Zooms out by a factor of 2.0
    uv *= 2.0;
    // Shifts every axis by -1.0
    uv -= 1.0;

    // Base color for the effect
    vec3 finalColor = vec3 ( .2, 1., 0. );

    finalColor *= abs(0.05 / (sin( uv.x + sin(uv.y+u_time)* 0.3 ) * 20.0) );

    gl_FragColor = vec4( finalColor, 1.0 );    
}

In vertex shader you need to pass uv coordinate to fragment shader:

attribute vec4 vPosition;
attribute vec4 uv;
uniform mat4 uMVPMatrix;
varying vec2 v_uv;

void main() 
{
    v_uv = uv;
    gl_Position = uMVPMatrix * vPosition;
}

Also you will have to create one more vertex buffer for uv coordinates, or pack uv coordinates into existing buffer. Then you will need to perform all the actions that you did for vertex attribute vPosition also for the new uv attribute. I mean, you need to perform glGetAttribLocation, glEnableVertexAttribArray and glVertexAttribPointer for the uv attribute.

Here is a tutorial, that may help you.

I've wrote a small example by use of threejs:

   var container;
   var camera, scene, renderer;
   var mesh;
   var uniforms;

   var clock = new THREE.Clock();

   init();
   animate();

   function init() {
     container = document.getElementById('container');

     camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
     camera.position.z = 2.0;
     camera.position.y = 1.0;
     camera.rotation.x = -0.45;

     scene = new THREE.Scene();

     var boxGeometry = new THREE.CubeGeometry(0.75, 0.75, 0.75);

     uniforms = {u_time: {type: "f", value: 0.0 } };

     var material = new THREE.ShaderMaterial({
       uniforms: uniforms,
       vertexShader: document.getElementById('vertexShader').textContent,
       fragmentShader: document.getElementById('fragment_shader').textContent
     });

     mesh = new THREE.Mesh(boxGeometry, material);
     scene.add(mesh);

     renderer = new THREE.WebGLRenderer();
     renderer.setClearColor( 0xffffff, 1 );
     container.appendChild(renderer.domElement);

     onWindowResize();

     window.addEventListener('resize', onWindowResize, false);

   }

   function onWindowResize(event) {
     camera.aspect = window.innerWidth / window.innerHeight;
     camera.updateProjectionMatrix();
     renderer.setSize(window.innerWidth, window.innerHeight);
   }

   function animate() {
     requestAnimationFrame(animate);
     render();
   }

   function render() {
     var delta = clock.getDelta();
     uniforms.u_time.value += delta;
     mesh.rotation.y += delta * 0.5;
     renderer.render(scene, camera);
   }
body { margin: 0px; overflow: hidden; }
<script src="http://threejs.org/build/three.min.js"></script>
<div id="container"></div>

<script id="fragment_shader" type="x-shader/x-fragment">
    uniform float u_time;
	varying vec2 v_uv;
    
    void main( void ) {
        vec2 uv = v_uv;
        // Zooms out by a factor of 2.0
        uv *= 2.0;
        // Shifts every axis by -1.0
        uv -= 1.0;
        
        // Base color for the effect
        vec3 finalColor = vec3 ( .2, 1., 0. );
        
        finalColor *= abs(0.05 / (sin( uv.x + sin(uv.y+u_time)* 0.3 ) * 20.0) );
    
        gl_FragColor = vec4( finalColor, 1.0 );    
    }
</script>

<script id="vertexShader" type="x-shader/x-vertex">
    varying vec2 v_uv;
                
    void main()
    {
		v_uv = uv;
		vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
		gl_Position = projectionMatrix * mvPosition;
    }
</script>

As an alternative, you can do not modify your java code at all, just calculate uv coordinates from object-space vertices coordinates of your square in the vertex shader and then pass them to the fragment shader.

Vertex shader:

attribute vec4 vPosition;
uniform mat4 uMVPMatrix;
varying vec2 v_uv;

void main() 
{
    v_uv = vPosition.xy + vec2(0.5);      //this expression depends on the actual vertex coordinates values.
    gl_Position = uMVPMatrix * vPosition;
}

Fragment shader would be the same.

UPDATE

I thought, that you want exactly the same line on your square. If you want just some glow effect without using textures, you may use distance field. For rectangle, distance field can be calculated as simple as:

float distanceField = length(max(abs(uv)-rectangleSize,0.0));

Where rectangleSize size of the rectangle in uv mapping, uv uv coordinate of the point of interest. To have distance field mapped as: 0.0 - point is inside rectangle, 1.0 point is on the far edge of the border. You may do the following thing:

float distanceField = length(max(abs(uv)-rectangleSize,0.0) / borderSize);

Where borderSize size of border in uv mapping.

So, your final fragment shader will be:

varying vec2 v_uv;

void main( void ) {
    vec2 uv = v_uv;
    // Zooms out by a factor of 2.0
    uv *= 2.0;
    // Shifts every axis by -1.0
    uv -= 1.0;

    // Base color for the effect
    vec3 color = vec3 ( .2, 1., 0. );

    // specify size of border. 0.0 - no border, 1.0 - border occupies the entire space
    vec2 borderSize = vec2(0.3); 

    // size of rectangle in terms of uv 
    vec2 rectangleSize = vec2(1.0) - borderSize; 

    // distance field, 0.0 - point is inside rectangle, 1.0 point is on the far edge of the border.
    float distanceField = length(max(abs(uv)-rectangleSize,0.0) / borderSize);

    // calculate alpha accordingly to the value of the distance field
    float alpha = 1.0 - distanceField;

    gl_FragColor = vec4(color, alpha);    
}

Here is an example:

 var container;
   var camera, scene, renderer;
   var mesh;
   var uniforms;

   var clock = new THREE.Clock();

   init();
   animate();

   function init() {
     container = document.getElementById('container');

     camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
     camera.position.z = 2.0;
     camera.position.y = 1.0;
     camera.rotation.x = -0.45;

     scene = new THREE.Scene();

     var boxGeometry = new THREE.PlaneGeometry(0.75, 0.75, 1);

     uniforms = {u_time: {type: "f", value: 0.0 } };

     var material = new THREE.ShaderMaterial({
       uniforms: uniforms,
       side: THREE.DoubleSide, 
       transparent: true,
       vertexShader: document.getElementById('vertexShader').textContent,
       fragmentShader: document.getElementById('fragment_shader').textContent
     });

     mesh = new THREE.Mesh(boxGeometry, material);
     scene.add(mesh);

     renderer = new THREE.WebGLRenderer();
     renderer.setClearColor( 0xffffff, 1 );
     container.appendChild(renderer.domElement);

     onWindowResize();

     window.addEventListener('resize', onWindowResize, false);

   }

   function onWindowResize(event) {
     camera.aspect = window.innerWidth / window.innerHeight;
     camera.updateProjectionMatrix();
     renderer.setSize(window.innerWidth, window.innerHeight);
   }

   function animate() {
     requestAnimationFrame(animate);
     render();
   }

   function render() {
     var delta = clock.getDelta();
     uniforms.u_time.value += delta;
     mesh.rotation.y += delta * 0.5;
     renderer.render(scene, camera);
   }
body { margin: 0px; overflow: hidden; }
<script src="http://threejs.org/build/three.min.js"></script>
<div id="container"></div>

<script id="fragment_shader" type="x-shader/x-fragment">
	varying vec2 v_uv;
    
    void main( void ) {
        vec2 uv = v_uv;
        // Zooms out by a factor of 2.0
        uv *= 2.0;
        // Shifts every axis by -1.0
        uv -= 1.0;
        
        // Base color for the effect
        vec3 color = vec3 ( .2, 1., 0. );
    
        // specify size of border. 0.0 - no border, 1.0 - border occupies the entire space
        vec2 borderSize = vec2(0.3); 
    
        // size of rectangle in terms of uv 
        vec2 rectangleSize = vec2(1.0) - borderSize; 
    
        // distance field, 0.0 - point is inside rectangle, 1.0 point is on the far edge of the border.
        float distanceField = length(max(abs(uv)-rectangleSize,0.0) / borderSize);
        
        // calculate alpha accordingly to the value of the distance field
        float alpha = 1.0 - distanceField;
    
        gl_FragColor = vec4(color, alpha);    
    }
</script>

<script id="vertexShader" type="x-shader/x-vertex">
    varying vec2 v_uv;
                
    void main()
    {
		v_uv = uv;
		vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
		gl_Position = projectionMatrix * mvPosition;
    }
</script>
Medley answered 17/5, 2015 at 14:26 Comment(2)
thanks for reply! please correct me if i've misunderstood but this would only apply the glowed effect to a line on the Square? I'm looking for the glow effect to cover the entire SquareFloccule
@bobbyrne01, yes, I thought you want this glowing line on your square :). I've updated my answer to show how to achieve simple glowing effect.Medley

© 2022 - 2024 — McMap. All rights reserved.