0

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

谁能解释一下我该如何做这份工作?我不知道这些照片是如何绘制的。

谢谢!

ps 可能是可压缩流体中的行波?

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

打印屏幕

第二个打印屏幕

第三个打印屏幕

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

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

2 回答 2

2

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

猜测是“等离子”效果,这在演示场景中曾经很常见。您可以在 Tempest 2000 PC 版菜单的背景中看到一个,包括在这个 YouTube 视频中非常简短的内容。这看起来对吗?

如果是这样,那么与所有演示效果一样,它就是烟雾和镜子。要在 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 运行一次,这给了我这个:

YouTube 链接

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

编辑 2:它不会对您有太大帮助,但我已将此 GL ES 代码放入合适的 iOS 包装器并上传到 GitHub

于 2013-09-16T23:42:10.820 回答
1

Android NDK 中有一个名为“bitmap-plasma”的示例程序,它会产生与此非常相似的模式。它是用 C 语言编写的,但可能可以转换为 GLSL 代码。

于 2013-09-17T22:15:00.830 回答