使用没有纹理的 glsl 将发光效果应用于正方形

Applying glow effect to a square using glsl without texture

我从 Android OpenGL 教程中获取了一些相同的代码,我想知道是否可以实现这里看到的发光效果:

http://glslsandbox.com/e#25224.0

使用下面的 Square 实现?即不使用纹理?我想将这种发光效果应用于整个 Square,即填充

上面的 link 使用了一个 resolution 变量,我不确定如果我试图对我的形状施加影响是否需要这个。我假设不需要 time 变量?

我在网上看到很多使用片段着色器产生发光效果的例子,但大多数都使用纹理。

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);
    }
}

使用 resolution 变量的唯一原因只是为了获得有效的 uv 映射。通常,我会建议您将纹理坐标(uv 映射)添加到您的 Square。您不必使用纹理,只需使用纹理坐标。 在这种情况下,您的片段着色器将是:

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 );    
}

在顶点着色器中,您需要将 uv 坐标传递给片段着色器:

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

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

此外,您还必须为 uv 坐标再创建一个顶点缓冲区,或者将 uv 坐标打包到现有缓冲区中。 然后,您需要对顶点属性 vPosition 以及新的 uv 属性执行所有操作。我的意思是,您需要为 uv 执行 glGetAttribLocationglEnableVertexAttribArrayglVertexAttribPointer 属性。

Here是一个教程,可能对你有帮助。

我用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>

作为替代方案,您可以根本不修改 java 代码,只需从对象计算 uv 坐标 - space 顶点着色器中正方形的顶点坐标,然后将它们传递给片段着色器。

顶点着色器:

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;
}

片段着色器也一样。

更新

我想,你想要在你的广场上完全相同的线。如果你只想要一些发光效果而不使用纹理,你可以使用距离场。 对于矩形,距离场可以简单地计算为:

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

其中 rectangleSize uv 映射中矩形的大小,uv 兴趣点的 uv 坐标。 将距离场映射为: 0.0 - 点在矩形内部,1.0 点在边界的远边缘。 您可以执行以下操作:

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

其中 borderSize uv 映射中边框的大小。

因此,您的最终片段着色器将是:

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);    
}

这是一个例子:

 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>