使用GPGPU对计算进行加速

从渲染管线到GPGPU

当我们使用渲染管线进行通用计算而不是进行渲染计算时,意味着我们使用了GPU进行了绘制像素以外的其它用途。在WebGL中我们将Texture看作一个多维数组时,通过shader中的计算对这些多维数组进行读取与计算,并且将计算结果输出,上述几句话就简要概括了GPGPU的根本使用方式。

这里设计到了几个关键概念:

  • 将Texture看作多维数组
  • 通过shader读取多维数组并进行计算
  • 将计算结果输出

这里假定读者已经具备对于渲染管线知识的掌握,仅描述如何使用渲染管线进行通用计算,使用WebGL作为代码示例

将Texture看作多维数组计算以及输出

一张Texture一般包含了4个通道,我们可以将其看作d*d*c大小的矩阵,每一个channel中,我们可以存储不同的数据,这样能够通过加载Texture的方式,将所需要进行加速加速的数据放置到GPU中。

const resolution = {
    x : 4,
    y : 4
}
// 载入数据,假设数据仅为单通道
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, 
	resolution.x, 
	resolution.y, 
	0, gl.LUMINANCE, gl.UNSIGNED_BYTE, 
	new Uint8Array([
        1, 2, 3, 4
        5, 6, 7, 8
        9, 10,11,12
        13,14,15,16
]));

通过shader读取多维数组

在输入时,当我们确保Texture大小为矩形时,我们可以通过片元着色器中的texture2D函数正确的读出Texture。

我们可以用顶点模拟出一个 [ − 1 , 1 ] [-1,1] [1,1]的裁切空间,使用两个三角形创建出-1到+1的四边形顶点。

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  -1, -1,
   1, -1,
  -1,  1,
  -1,  1,
   1, -1,
   1,  1,
]), gl.STATIC_DRAW);

之后在片元着色器中获取这些数据共有两种方式

  1. 使用gl_FragCoord获取该像素点坐标,然后转换得到uv坐标:

    uniform vec2 u_resolution;     // 假设我们传入了该Texture的大小
    uniform sampler2D textureData; // 用于读取数据的纹理
    
    void main() {
        vec2 texCoord = gl_FragCoord.xy / resolution;
        // 从纹理中采样数据
        vec4 data = texture2D(textureData, texCoord);
        // 对数据进行一些计算
        vec4 result = someComputation(data);
        // 输出计算结果
        gl_FragColor = result;
    }
    
  2. 在顶点着色器中使用顶点数据传递,使用插值结果获得uv坐标

    vec4 a_position;
    varying vec2 index;
    
    void main() {
        index = (a_position.xy + 1.0) / 2.0; 
        gl_Position=a_position;
    }
    

    之后在片元着色器中,由于传递数据会自动在片元中进行插值,因此可以index即为uv坐标

    varying vec2 index;    // 从顶点着色器传递过来的纹理坐标
    uniform sampler2D textureData; // 用于读取数据的纹理
    
    void main() {
        vec4 data = texture2D(textureData, index);
        // 对数据进行一些计算
        vec4 result = someComputation(data);
        // 输出计算结果
        gl_FragColor = result;
    }
    

读取输出

使用gl.readPixels可以对输出进行读取,需要注意,该输出会将RGBA四个通道均进行输出

一个Slime模拟器

使用了twjs库,简化webgl代码

Slime模拟效果简述

模拟连接

该模拟来源于论文中对于Physarum polycephalum一种粘液真菌的扩散机制模拟,如下图所示
真菌移动机制
简言之,真菌在向前移动时,会在当前位置留下信息素,同时向移动方向释放传感器,文章中将传感器固定为三个方向,分别如图所示为F,FL以及FR。传感器会感知这三个方向上,之前真菌留下的轨迹信息素,最终真菌会更倾向选择向着之前真菌留下信息素的方向进行移动。如果真菌在移动中碰到障碍物,则会选择随机一个方向继续向前移动。

我们使用WebGL去创建模拟真菌移动的效果,并且使用GPGPU进行加速计算,得到了模拟器中所展示的效果。

模拟说明

位置与扩散信息记录

使用Texture(pos_tex)去记录当前每个真菌的位置(2d),以及方向角(1d)。Texture大小设置为1024*1024为四通道,其中rg通道记录位置信息,b通道记录角度信息。

    public convertToTextureData(positions: number[], angles: number[]) {
        const data = new Float32Array(1024 * 1024 * 4);
        for (let i = 0; i < angles.length; ++i) {
            const src = i * 2;
            const dst = i * 4;
            data[dst] = positions[src];
            data[dst + 1] = positions[src + 1];
            data[dst + 2] = angles[i];
        }
        return data;
    }
    public createAgents(positions: number[], angles: number[]) {
        const texture = twgl.createTexture(this.gl, {
            src: this.convertToTextureData(positions, angles),
            width: 1024,
            height: 1024,
            min: this.gl.NEAREST,
            wrap: this.gl.CLAMP_TO_EDGE,
        });
        const framebufferInfo = twgl.createFramebufferInfo(this.gl, [
            { attachment: texture, }
        ], 1024, 1024);
        return {
            texture,
            framebufferInfo,
        };
    }

同理使用相同大小的Texture(spread_tex)的去记录真菌在空间中留下信息素的情况,初始化设置为空Texture

位置更新与信息扩散模拟

位置更新遵循上述真菌移动模式,设置三个方向的传感器,对于spread_tex的信息进行采样比较,选取最终的前进方向

...
...
float sense(vec2 position,float sensorAngle){
    vec2 sensorDir=vec2(cos(sensorAngle),sin(sensorAngle));
    vec2 sensorCenter=position+sensorDir*sensorOffsetDist;
    vec2 sensorUV=sensorCenter/resolution;
    vec4 s=texture2D(spread_tex,sensorUV,sensorSize);
    return s.r;
}
void main(){
    float vertexId=floor(gl_FragCoord.y*agentsTexResolution.x+gl_FragCoord.x);
    vec2 uv=gl_FragCoord.xy/agentsTexResolution;
    vec4 temp=texture2D(tex_pos,uv);
    vec2 position=temp.rg; // rg --> pos
    float angle=temp.b; // b --> angle
    float width=resolution.x;
    float height=resolution.y;
    float random=hash(vertexId/(agentsTexResolution.x*agentsTexResolution.y)+time);
    // sense environment
    float weightForward=sense(position,angle);
    float weightLeft=sense(position,angle+sensorAngleSpacing);
    float weightRight=sense(position,angle-sensorAngleSpacing);
    
    float randomSteerStrength=random;
    
    if(weightForward>weightLeft&&weightForward>weightRight){
        angle+=0.; //F
    }else if(weightForward<weightLeft&&weightForward<weightRight){
        angle+=(randomSteerStrength-.5)*2.*turnSpeed*deltaTime;
    }else if(weightRight>weightLeft){
        angle-=randomSteerStrength*turnSpeed*deltaTime; //FR
    }else if(weightLeft>weightRight){
        angle+=randomSteerStrength*turnSpeed*deltaTime; //FL
    }

    // move agent
    vec2 direction=vec2(cos(angle),sin(angle));
    vec2 newPos=position+direction*moveSpeed*deltaTime;
    // clamp to boundries and pick random angle if hit boundries  
    ...  
    gl_FragColor=vec4(newPos,angle,0);
}

同时在spread_tex中模拟信息素的扩散,采样当前位置的信息强度,并且与周围3*3大小的区域进行平滑处理,利用超参数控制信息的扩散(diffuseSpeed)以及蒸发(evaporateSpeed)速度

void main(){
  vec4 originalValue=texture2D(tex,v_texcoord);
  // Simulate diffuse 
  vec4 sum;
  for(int offsetY=-1;offsetY<=1;++offsetY){
    for(int offsetX=-1;offsetX<=1;++offsetX){
      vec2 sampleOff=vec2(offsetX,offsetY)/resolution;
      sum+=texture2D(tex,v_texcoord+sampleOff);
    }
  }
  vec4 blurResult=sum/9.;
  vec4 diffusedValue=mix(originalValue,blurResult,diffuseSpeed*deltaTime);
  vec4 diffusedAndEvaporatedValue=max(vec4(0),diffusedValue-evaporateSpeed*deltaTime);
  gl_FragColor=vec4(diffusedAndEvaporatedValue.rgb,1);
}

效果可视化

根据信息素强度以及真菌所在位置,对模拟结果进行可视化。生成了16种模拟的配色方案,将其存储在一张Texture上,按照位置与强度对贴图进行采样得到最终可视化效果
配色方案

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值