如何使用OpenGL ES 2.0在Android中绘制2D水?

时间:2013-09-16 22:35:15

标签: android graphics opengl-es-2.0

我正在用Java开发Android应用程序。我想绘制动态图像,如附加文件(非常旧的DOS程序的打印屏幕)。我认为这是水波。

任何人都能解释我怎么能做这份工作?我不知道这些照片是如何绘制的。

谢谢!

P.S。可能是可压缩流体中的行波?

已编辑:包含所需动画的屏幕记录:http://www.youtube.com/watch?v=_zeSQX_8grY

print screen

second print screen

third print screen

EDITED2:我找到了此视频效果的来源here。有一个DOS编译程序(可以在DOS框中运行)和ASM中的课程。文件夹“PART3”包含所需视频效果的来源(文件WPLASMA.ASM)。不幸的是我不知道Turbo Assembler。有人可以帮我理解这个节目如何吸引这种视频效果吗?我发布了WPLASMA.ASM here的内容。

EDITED3:我已将代码的大部分内容移植到C.但我不知道VGA模式是如何工作的。我对PutBmp功能有困难。

#include <cmath>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cassert>

#include <opencv2/highgui/highgui.hpp>

struct RGB {
    char red, green, blue;
};

#define MAXH 60           // horiz wave length.
#define MAXVW 64          // vert wave length.
#define MAXHW 32          // max horiz wave amount.
#define MAXV (80 + MAXHW) // vert wave length.

static void UpdHWaves( char* HWave1, char* HWave2,
                       int& HWavPos1, int& HWavPos2,
                       int HWavInc1 ) // Updates the Horiz Waves.
{
    for( int i = 0; i < MAXH - 1; ++i ) {
        HWave1[ i ] = HWave1[ i + 1 ];
    }

    int8_t val = 127 * std::sin( HWavPos1 * M_PI / 180.0 );
    HWave1[ MAXH - 1 ] =  val >> 1;

    HWavPos1 += HWavInc1;
    if( HWavPos1 >= 360 ) {
        HWavPos1 -= 360;
    }

    for( int i = 0; i < MAXH; ++i ) {
        val = 127 * std::sin( ( HWavPos2 + i * 4 ) * M_PI / 180.0 );
        val = ( val >> 1 ) + HWave1[ i ];
        HWave2[ i ] = ( val >> 3 ) + 16;
    }

    HWavPos2 += 4;
    if( HWavPos2 >= 360 ) {
        HWavPos2 -= 360;
    }
}

static void UpdVWaves( char *VWave1, char* VWave2,
                       int& VWavPos1, int& VWavPos2,
                       int VWavInc1 )
{
    for( int i = 0; i < MAXV - 1; ++i ) {
        VWave1[ i ] = VWave1[ i + 1 ];
    }

    int8_t val = 127 * std::sin( VWavPos1 * M_PI / 180.0 );
    VWave1[ MAXV - 1 ] = val >> 1;

    VWavPos1 += VWavInc1;
    if( VWavPos1 >= 360 ) {
        VWavPos1 -= 360;
    }

    for( int i = 0; i < MAXV; ++i ) {
        val = 127 * std::sin( ( VWavPos2 + i * 3 ) * M_PI / 180.0 );
        val = ( val >> 1 ) + VWave1[ i ];
        VWave2[ i ] = ( val >> 2 ) + 32;
    }

    ++VWavPos2;
    if( VWavPos2 >= 360 ) {
        VWavPos2 -= 360;
    }
}

static void UpdBmp( char *Bitmap, const char *VWave2 ) // Updates the Plasma bitmap.
{
    for( int k = 0; k < MAXV; ++k ) {
        char al = VWave2[ k ];
        int i = 0;
        for( int l = 0; l < MAXH; ++l ) {
            ++al;
            Bitmap[ i ] = al;
            i += 256;
        }
        ++Bitmap;
    }
}

static void PutBmp( const RGB* palete,
                    const char* BitMap,
                    const char* HWave2 ) // Puts into the screen the Plasma bitmap.
{
    RGB screen[320*200];
    memset( screen, 0, sizeof( screen ) );
    RGB *screenPtr = screen;

    const char *dx = BitMap;
    const char *si = HWave2;

    for( int i = 0; i < MAXH; ++i ) {
        char ax = *si;
        ++si;

        const char *si2 = ax + dx;
        for( int j = 0; j < 40; ++j ) {
            assert( *si2 < MAXH + MAXVW );
            *screenPtr = palete[ *si2 ];
            ++screenPtr;
            ++si2;

            assert( *si2 < MAXH + MAXVW );
            *screenPtr = palete[ *si2 ];
            ++screenPtr;
            ++si2;
        }
        dx += 256;
    }

    static cv::VideoWriter writer( "test.avi", CV_FOURCC('M','J','P','G'), 15, cv::Size( 320, 200 ) );

    cv::Mat image( 200, 320, CV_8UC3 );
    for( int i = 0; i < 200; ++i ) {
        for( int j = 0; j < 320; ++j ) {
            image.at<cv::Vec3b>(i, j )[0] = screen[ 320 * i + j ].blue;
            image.at<cv::Vec3b>(i, j )[1] = screen[ 320 * i + j ].green;
            image.at<cv::Vec3b>(i, j )[2] = screen[ 320 * i + j ].red;
        }
    }

    writer.write( image );
}

int main( )
{
    RGB palete[256];
    // generation of the plasma palette.
    palete[ 0 ].red = 0;
    palete[ 0 ].green = 0;
    palete[ 0 ].blue = 0;
    RGB *ptr = palete + 1;
    int ah = 0;
    int bl = 2;
    for( int i = 0; i < MAXH + MAXVW; ++i ) {
        ptr->red = 32 - ( ah >> 1 );
        ptr->green = 16 - ( ah >> 2 );
        ptr->blue = 63 - ( ah >> 2 );
        ah += bl;
        if( ah >= 64 ) {
            bl = - bl;
            ah += 2 * bl;
        }
        ptr += 1;
    }

    //setup wave parameters.
    int HWavPos1 = 0; // horiz waves pos.
    int HWavPos2 = 0;
    int VWavPos1 = 0; // vert waves pos.
    int VWavPos2 = 0;
    int HWavInc1 = 1; // horiz wave speed.
    int VWavInc1 = 7; // vert wave speed.

    char HWave1[ MAXH ]; // horiz waves.
    char HWave2[ MAXH ];
    char VWave1[ MAXV ]; // vert waves.
    char VWave2[ MAXV ];

    char Bitmap[ 256 * MAXH + MAXV ];
    memset( Bitmap, 0, sizeof( Bitmap ) );

    //use enough steps to update all the waves entries.
    for( int i = 0; i < MAXV; ++i ) {
        UpdHWaves( HWave1, HWave2, HWavPos1, HWavPos2, HWavInc1 );
        UpdVWaves( VWave1, VWave2, VWavPos1, VWavPos2, VWavInc1 );
    }

    std::srand(std::time(0));
    for( int i = 0; i < 200; ++i ) {
        UpdHWaves( HWave1, HWave2, HWavPos1, HWavPos2, HWavInc1 );
        UpdVWaves( VWave1, VWave2, VWavPos1, VWavPos2, VWavInc1 );
        UpdBmp( Bitmap, VWave2 );
        PutBmp( palete, Bitmap, HWave2 );

        //change wave's speed.
        HWavInc1 = ( std::rand( ) & 7 ) + 3;
        VWavInc1 = ( std::rand( ) & 3 ) + 5;
    }

    return 0;
}

2 个答案:

答案 0 :(得分:2)

你能说出DOS程序吗?或者在YouTube上找到类似的效果?

猜测,它是一种“等离子”效应,曾经在演示场景中很常见。您可以在Tempest 2000的PC版本菜单的背景中看到一个,包括this YouTube video中的非常短暂的内容。那看起来不错吗?

如果是这样,那么就像所有演示效果一样,它是烟雾和镜子。要在OpenGL中重新创建一个,您需要生成具有球形正弦图案的纹理。因此,对于每个像素,计算出距离中心的距离。得到那个距离的正弦乘以你认为美学上令人愉悦的数字。将该值存储到纹理中。确保缩放以填充整个字节。你应该得到一个看起来像池塘表面涟漪的图像。

要产生最终输出,您将至少合并其中三个。如果你要做三个然后将每个乘以1/3,那么帧缓冲中的值最终会在0-255范围内。你将独立地移动这三个东西以产生动画,也可以通过正弦函数 - 例如一个人可能会遵循路径centre + (0.3 * sin(1.8 + time * 1.5), 0.8 * sin(0.2 + time * 9.2)),其他人也会遵循该形式的功能。根据需要调整时间倍数,角度偏移和轴倍增器。

还有一个正弦模式可以应用:如果这是一个DOS程序,你还需要设置你的调色板,使亮度以正弦波形式出现 - 例如颜色0-31将是一个完整的循环,32-63将是循环的重复,等等。您无法在现代设备上设置调色板,OpenGL ES不会执行调色板纹理,因此您将不得不写一个着色器。从好的方面来说,三角函数是内置于GLSL中的,因此它将是一个相当简单的函数。

编辑:我把一个快速测试项目汇总在一起并编写了以下顶点着色器:

attribute vec4 position;
attribute vec2 texCoord;

uniform mediump float time;

varying highp vec2 texCoordVarying1, texCoordVarying2, texCoordVarying3;

void main()
{
    mediump float radiansTime = time * 3.141592654 * 2.0;

    /*
        So, coordinates here are of the form:

            texCoord + vec2(something, variant of same thing)

        Where something is:

            <linear offset> + sin(<angular offset> + radiansTime * <multiplier>)


        What we're looking to do is to act as though moving three separate sheets across
        the surface. Each has its own texCoordVarying. Each moves according to a
        sinusoidal pattern. Note that the multiplier is always a whole number so
        that all patterns repeat properly as time goes from 0 to 1 and then back to 0,
        hence radiansTime goes from 0 to 2pi and then back to 0.

        The various constants aren't sourced from anything. Just play around with them.

    */

    texCoordVarying1 = texCoord + vec2(0.0 + sin(0.0 + radiansTime * 1.0) * 0.2, 0.0 + sin(1.9 + radiansTime * 8.0) * 0.4);
    texCoordVarying2 = texCoord - vec2(0.2 - sin(0.8 + radiansTime * 2.0) * 0.2, 0.6 - sin(1.3 + radiansTime * 3.0) * 0.8);
    texCoordVarying3 = texCoord + vec2(0.4 + sin(0.7 + radiansTime * 5.0) * 0.2, 0.5 + sin(0.2 + radiansTime * 9.0) * 0.1);

    gl_Position = position;
}

...和片段着色器:

varying highp vec2 texCoordVarying1, texCoordVarying2, texCoordVarying3;

void main()
{
    /*
        Each sheet is coloured individually to look like ripples on
        the surface of a pond after a stone has been thrown in. So it's
        a sine function on distance from the centre. We adjust the ripple
        size with a quick multiplier.

        Rule of thumb: bigger multiplier = smaller details on screen.

    */
    mediump vec3 distances =
        vec3(
            sin(length(texCoordVarying1) * 18.0),
            sin(length(texCoordVarying2) * 14.2),
            sin(length(texCoordVarying3) * 11.9)
        );

    /*
        We work out outputColour in the range 0.0 to 1.0 by adding them,
        and using the sine of that.
    */
    mediump float outputColour = 0.5 + sin(dot(distances, vec3(1.0, 1.0, 1.0)))*0.5;

    /*
        Finally the fragment colour is created by linearly interpolating
        in the range of the selected start and end colours 48 36 208
    */
    gl_FragColor =
        mix( vec4(0.37, 0.5, 1.0, 1.0), vec4(0.17, 0.1, 0.8, 1.0), outputColour);
}

/*
    Implementation notes:

        it'd be smarter to adjust the two vectors passed to mix so as not
        to have to scale the outputColour, leaving it in the range -1.0 to 1.0
        but this way makes it clearer overall what's going on with the colours
*/

将它放入一个绘制四边形的项目中,以显示两个维度中的texCoord范围[0,1](宽高比被诅咒)和设置时间,使其每分钟从0到1运行一次,这给了我: / p>

YouTube link

它显然不完全相同,但效果相同。你需要调整各种魔法常数,直到得到你满意的东西。

编辑2:它不会帮助你那么多,但我把这个GL ES代码放到合适的iOS包装器中uploaded to GitHub

答案 1 :(得分:1)

Android NDK中有一个名为“bitmap-plasma”的示例程序,它会产生非常相似的模式。它在C中,但可能会转换为GLSL代码。