倒水游戏动态绘画效果深度解析:从像素分析到Shader渲染的完整技术实现

倒水游戏动态绘画效果深度解析:从像素分析到Shader渲染的完整技术实现

在倒水游戏中,我实现了一种独特的动态绘画效果,让水彩颜料像真实水流一样在画布上扩散、融合。这种效果不仅增强了游戏的艺术表现力,也为玩家带来了全新的视觉体验。本文将深入解析这一效果的技术实现方案,从像素分析到Shader渲染的完整技术栈。

效果演示

Demo发布信息

录屏1

核心技术架构

1. 整体设计思路

动态绘画效果的核心思想是将静态图像分解为多个颜色层,然后按照预定顺序和时间间隔逐层渲染,模拟水彩颜料在画布上自然扩散的过程。

// 核心数据结构:颜色列范围数组
// [颜色索引][线段索引][x坐标, 起始y, 结束y, 高度, 延迟时间]
protected colorColumnRangesList: [number, number, number, number, number][][] = [];

2. 核心技术组件:VwDrawingBoard

VwDrawingBoard 组件是整个效果的核心实现,负责管理图像加载、颜色分析、动画控制和渲染输出。

2.1 组件初始化与资源配置

/**
 * 重置绘图板状态并加载新图像
 * @param imageAsset 待绘制的图像资源
 * @param colorHexs 颜色配置数组
 */
public async reset(imageAsset: ImageAsset, colorHexs: string[]) {
    this.colorPalette = [];
    colorHexs.forEach(colorHex => {
        this.colorPalette.push(new Color().fromHEX(colorHex));
    });
    this.loadImageAsset(imageAsset);
    this.initializeDrawingBoardState();
}

2.2 遮罩纹理系统

遮罩纹理是实现动态显示的关键技术,通过控制每个像素的透明度来实现渐进式显示效果:

protected async setupMaskTextureAndLoadImage() {
    // 根据图形API选择像素格式
    const pixelFormat = this.bytesPerPixel == 1 ? 
        Texture2D.PixelFormat.A8 : Texture2D.PixelFormat.RGBA8888;
    
    // 创建遮罩纹理
    this.maskTextureInstance = new Texture2D();
    this.maskTextureInstance.reset({
        width: this.imageWidth, 
        height: this.imageHeight, 
        format: pixelFormat
    });

    // 初始化为全透明
    this.maskTextureByteData = new Uint8Array(
        this.imageWidth * this.imageHeight * this.bytesPerPixel
    );
    this.maskTextureByteData.fill(0);
    
    // 上传纹理数据到GPU
    this.maskTextureInstance.uploadData(this.maskTextureByteData);
    this.maskMaterialInstance.setProperty('maskTexture', this.maskTextureInstance);
}

录屏a

核心算法:像素分析与颜色分层

3.1 颜色相似度计算

使用欧几里得距离算法计算像素颜色与目标颜色的相似度:

protected generateColorColumnRanges(pixelBuffer: Uint8Array) {
    for (let x = 0; x < this.imageWidth; x++) {
        for (let y = 0; y < this.imageHeight; y++) {
            const pixelIndex = (x + y * this.imageWidth) * 4;
            let minColorDistance = Number.MAX_SAFE_INTEGER;
            let closestColorIndex = -1;
            
            // 寻找与当前像素最接近的颜色
            for (let colorIndex = 0; colorIndex < this.colorPalette.length; colorIndex++) {
                const deltaR = this.colorPalette[colorIndex].r - pixelBuffer[pixelIndex];
                const deltaG = this.colorPalette[colorIndex].g - pixelBuffer[pixelIndex + 1];
                const deltaB = this.colorPalette[colorIndex].b - pixelBuffer[pixelIndex + 2];
                const colorDistance = Math.sqrt(deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
                
                if (colorDistance < minColorDistance) {
                    minColorDistance = colorDistance;
                    closestColorIndex = colorIndex;
                }
            }
            
            // 将像素归类到对应的颜色层
            // ...
        }
    }
}

3.2 垂直线段合并算法

为了优化性能,将垂直方向上连续的相同颜色像素合并为线段:

// 在垂直方向上扫描,合并连续的同色像素
for (let y = 0; y < this.imageHeight; y++) {
    // 如果颜色变化,结束当前线段并开始新线段
    if (!currentRange || (closestColorIndex !== lastMatchedColorIndex)) {
        currentRange && finalizeRange(currentRange, lastMatchedColorIndex);
        currentRange = [x, y, y, 0, 0];
        lastMatchedColorIndex = closestColorIndex;
        colorRangesByLayer[closestColorIndex].push(currentRange);
    } else {
        // 颜色相同,扩展当前线段
        currentRange[2] = y;
    }
}

录屏2

动画时序控制系统

4.1 智能时序规划

为了确保动画效果的自然流畅,系统需要智能规划各颜色层的绘制顺序和时间:

protected calculateAnimationTimings() {
    // 按线段数量排序,线段多的优先绘制
    const sortableColorLayers: any[] = []
    for (let i = 0, len = totalColorLayers; i < len; i++) {
        const rangeList = this.colorColumnRangesList[i];
        sortableColorLayers.push({
            ranges: rangeList, 
            index: i, 
            len: rangeList.length
        });
    }
    sortableColorLayers.sort((a, b) => b.len - a.len);

    // 随机确定绘制方向,增加视觉效果的自然性
    this.colorColumnRangesList.forEach((rangeList, layerIndex) => {
        if (Math.random() > 0.5) {
            this.colorColumnRangesList[layerIndex].reverse();
        }
    })

    // 计算每个线段的出现时间,确保总绘制时间合理
    for (let i = 0, len = totalColorLayers; i < len; i++) {
        let currentDelay = Math.min(initialDelay, 2) + 1 + i * 1.5;
        const rangeList = this.colorColumnRangesList[i];
        const totalRanges = rangeList.length;
        
        let totalHeight = 0;
        rangeList.forEach((range) => totalHeight += range[3]);
        
        // 动态计算绘制速度,确保单层绘制时间不超过3秒
        const drawSpeed = Math.max(totalHeight / 3, this.baseAnimationSpeed);
        
        for (let j = 0; j < totalRanges; j++) {
            const range = rangeList[j];
            range[4] = currentDelay;
            currentDelay += range[3] / drawSpeed;
        }
    }
}

4.2 实时动画更新

系统通过每帧更新机制实现平滑的动画效果:

protected update(dt: number): void {
    // 更新绘制动画
    if (this.isDrawingAnimationActive) {
        this.updateDrawingAnimation(dt);
    }
    // 更新透明度渐变
    if (this.isAlphaAnimationActive) {
        this.updateAlphaFadeAnimation(dt);
    }

    // 如果纹理有变化,上传到GPU
    if (this.hasTextureChanged && this.maskTextureByteData && this.maskTextureInstance) {
        this.maskTextureInstance.uploadData(this.maskTextureByteData);
        this.maskMaterialInstance.setProperty('maskTexture', this.maskTextureInstance);
    }
}

渐进式渲染技术

5.1 平滑渐显效果

为了实现自然的渐显效果,系统采用渐进式渲染策略:

private renderRangeGradually(range: [number, number, number, number, number]) {
    let currentY = range[1];
    if (currentY < range[2]) {
        // 计算本帧应渲染到的y坐标
        currentY += range[4];
        // 动态加速渐变速度
        if (range[4] < this.alphaFadeSpeed) {
            range[4]++;
        }
        // 限制不超过目标y坐标
        if (currentY > range[2]) {
            currentY = range[2];
        }
        
        // 填充像素数据(设置为完全不透明)
        for (let y = range[1]; y <= currentY; y++) {
            const pixelIndex = (range[0] + y * this.imageWidth) * this.bytesPerPixel;
            for (let byteOffset = 0; byteOffset < this.bytesPerPixel; byteOffset++) {
                this.maskTextureByteData[pixelIndex + byteOffset] = 255;
            }
        }
        range[1] = currentY;
        return true;
    }
    return false;
}

性能优化策略

6.1 渲染优化

  • 垂直方向约束:只在垂直方向合并像素,减少计算复杂度
  • 颜色量化:使用有限的颜色调色板,减少颜色匹配计算量
  • 缓冲区管理:合理管理纹理缓冲区,减少GPU上传次数

录屏b

技术亮点总结

  1. 智能颜色分析:基于欧几里得距离的颜色相似度算法,准确识别图像中的主要颜色区域
  2. 自然动画时序:动态计算绘制速度和时间间隔,确保动画效果的自然流畅
  3. 渐进式渲染:平滑的渐显效果模拟真实水彩扩散过程
  4. 跨平台兼容:支持WebGL、WebGL 2.0和WebGPU等多种图形API
  5. 性能优化:通过线段合并、延迟渲染等技术确保在移动设备上的流畅运行

应用场景扩展

这种动态绘画效果技术不仅可以应用于倒水游戏,还可以扩展到:

  • 教育应用:书法教学、绘画教程中的笔迹演示
  • 艺术创作:数字艺术作品的动态展示
  • UI动效:应用启动画面、功能引导的渐进式显示
  • 广告创意:品牌Logo的动态绘制效果

结语

通过像素分析、颜色分层和渐进式渲染技术的结合,我成功实现了倒水游戏中独特的动态绘画效果。这种技术方案不仅具有很好的视觉效果,还在性能优化方面做了充分考虑,确保了在各种设备上的流畅运行。

3赞

效果太棒了

1赞