自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

PixiJS 源碼解讀:繪制矩形的渲染過程講解

開發(fā) 前端
PixiJS 繪制圖形使用了 WebGL,為了利用 GPU 的并行能力,需要給著色器一次性提供盡可能多的頂點和顏色信息。PixiJS 提供了一些基礎(chǔ)圖形,比如矩形。繪制時會根據(jù)圖形屬性信息進行三角化,最后將所有的信息組合起來,一次性提供給 WebGL。

大家好,我是前端西瓜哥。

之前寫了一篇 PixiJS 繪制矩形,簡單說了一下 PixiJS 是怎么繪制矩形的。

《PixiJS 源碼解讀:繪制矩形,底層都做了什么?》

它更多的講解上層的東西,沒花太多筆墨描繪底層渲染的流程。所以我寫了這篇文章,對渲染流程進行補充講解。

PixiJS 版本為 7.2.4。

要求讀者熟悉 WebGL 的基礎(chǔ)知識。

本文會 以繪制設(shè)置了填充和描邊的矩形為例子,看底層 WebGL 的調(diào)用執(zhí)行。

業(yè)務(wù)層代碼:

const app = new PIXI.Application({
  width: 500,
  height: 300,
  background: "#cc0", //(土黃色)
});
document.body.appendChild(app.view);

const graph = new PIXI.Graphics();
graph.beginFill(0xff0044); // 紅色填充色
graph.lineStyle({ color: "blue", width: 4 }); // 藍色描邊
graph.drawRect(90, 70, 300, 100);

app.stage.addChild(graph);

繪制結(jié)果為:

創(chuàng)建 gl

第一步是創(chuàng)建 gl 對象,上下文類型優(yōu)先使用 "webgl2"。

如果不支持,會降級為 "webgl"、"experimental-webgl"。

gl = canvas.getContext("webgl2", options);

gl 在 renderer 渲染器初始化的時候構(gòu)建的,可通過 app.renderer.gl 拿到。

構(gòu)建著色器代碼片段

定義 頂點著色器 和 片元著色器。

著色器(Shader)是一種類 C 語言 GLSL,用于描述需要繪制的 頂點信息和顏色信息。

著色器模板

首先是 字符串模板,等著根據(jù)配置填充成一個完整的著色器代碼片段。

頂點著色器的模板(后面會基于它生成真正可用的著色器)位于 packages/core/src/batch/texture.vert 中。

batch 文件夾都是和 批量繪制 有關(guān)的邏輯,批量、減少 draw call 正是 PixiJS 高效繪制的秘訣。

precision highp float;
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
attribute vec4 aColor;
attribute float aTextureId;

uniform mat3 projectionMatrix;
uniform mat3 translationMatrix;
uniform vec4 tint;

varying vec2 vTextureCoord;
varying vec4 vColor;
varying float vTextureId;

void main(void){
    gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);

    vTextureCoord = aTextureCoord;
    vTextureId = aTextureId;
    vColor = aColor * tint;
}

片元著色器和顏色有關(guān)。

varying vec2 vTextureCoord;
varying vec4 vColor;
varying float vTextureId;
uniform sampler2D uSamplers[%count%];

void main(void){
    vec4 color;
    %forloop%
    gl_FragColor = color * vColor;
}

這里的 %count% 和%forloop% 是占位符,會在之后進行替換。

最終著色器代碼片段

在 renderer 初始化時,上面的模板會進行一系列的改造,兩個著色器最終轉(zhuǎn)換為下面的樣子。

頂點著色器(Vertex Shader)和頂點的位置、大小有關(guān)。

補充一些簡單注釋說明。

頂點著色器

precision highp float; // 浮點數(shù)使用高精度
#define SHADER_NAME pixi-shader-2
precision highp float;
attribute vec2 aVertexPosition; // 頂點位置 x 和 y
attribute vec2 aTextureCoord; // 紋理坐標(biāo),會傳給片元著色器
attribute vec4 aColor; // 顏色,rgba,會傳給片元著色器
attribute float aTextureId; // 紋理單元 ID,會傳給片元著色器

uniform mat3 projectionMatrix; // 投影矩陣
uniform mat3 translationMatrix; // 平移變換矩陣
uniform vec4 tint; // 改變顏色,實現(xiàn)濾鏡效果,會和 aColor 相乘傳給片元著色器

varying vec2 vTextureCoord; // varing 都是用來傳遞的
varying vec4 vColor;
varying float vTextureId;

void main(void){
    //  進行一系列矩陣乘法運算,將最后的點傳給內(nèi)置的著色器變量,設(shè)置點的位置
    gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);

   // 下面都是要傳給片元著色器的變量
    vTextureCoord = aTextureCoord;
    vTextureId = aTextureId;
    vColor = aColor * tint;
}

片元著色器

片元著色器(Fragment Shader)用于描述頂點圍成區(qū)域的像素顏色。

下面是片元著色器的最終代碼,同樣我會加一些注釋說明

precision mediump float;
#define SHADER_NAME pixi-shader-2
varying vec2 vTextureCoord; // 紋理坐標(biāo),
varying vec4 vColor; // 顏色
varying float vTextureId; // 使用哪一個紋理采樣器
uniform sampler2D uSamplers[16]; // 16 個紋理采樣器

void main(void){
  vec4 color;
 
  if(vTextureId < 0.5) {
    // 從紋理采樣器(比如圖片轉(zhuǎn)換過來的像素點集合)中,提取特定位置的像素點
    color = texture2D(uSamplers[0], vTextureCoord);
  }else if(vTextureId < 1.5) {
    color = texture2D(uSamplers[1], vTextureCoord);
  }
  // ...
  } else {
    color = texture2D(uSamplers[15], vTextureCoord);
  }
  
  // 疊加顏色值,和紋理采樣器取得的顏色值,賦值給片元著色器內(nèi)置變量
  gl_FragColor = color * vColor;
}

如果沒有設(shè)置紋理,PixiJS 會給一個默認(rèn)的兜底用紋理對象,一個 16x16 的白色方形。

這兩個著色器片段會保存到 Shader 實例中,放到 app.render.shader 下。

編譯著色器程序

第一次調(diào)用 renderer 渲染器 render 方法時,PixiJS 會 創(chuàng)建頂點著色器對象和片元著色器對象。

這些邏輯是在 generateProgram 方法中實現(xiàn)的。該方法的核心代碼:

function generateProgram(gl, program) {
  //(1)創(chuàng)建頂點著色器對象、片元著色器對象等
  const glVertShader = compileShader(gl, gl.VERTEX_SHADER, program.vertexSrc);
  const glFragShader = compileShader(
    gl,
    gl.FRAGMENT_SHADER,
    program.fragmentSrc
  );

  // 創(chuàng)建程序?qū)ο?  const webGLProgram = gl.createProgram();

  //(2)綁定 attribute
  // keys 為 ['aColor', 'aTextureCoord', 'aTextureId', 'aVertexPosition']
  for (let i = 0; i < keys.length; i++) {
    program.attributeData[keys[i]].location = i;
    // 將屬性綁定到頂點著色器的制定位置
    // 如:gl.bindAttribLocation(gl.program, 0, "aColor");
    gl.bindAttribLocation(webGLProgram, i, keys[i]);
  }

  // 刪除著色器對象,釋放內(nèi)存
  gl.deleteShader(glVertShader);
  gl.deleteShader(glFragShader);

  //(3)綁定 uniformLocation(準(zhǔn)確來說是拿地址,還沒正式綁定)
  // 屬性(對應(yīng) i 變量)有:projectionMatrix、tint、translationMatrix、uSamplers
  for (const i in program.uniformData) {
    const data = program.uniformData[i];

    uniformData[i] = {
      location: gl.getUniformLocation(webGLProgram, i),
      value: defaultValue(data.type, data.size),
    };
  }

  const glProgram = new GLProgram(webGLProgram, uniformData);
  return glProgram;
}

分成三個主要步驟。

創(chuàng)建著色器對象、程序?qū)ο蟆?/p>

compileShader 實現(xiàn):

function compileShader(gl, type, src) {
  const shader = gl.createShader(type);

  gl.shaderSource(shader, src);
  gl.compileShader(shader);
  
  gl.attachShader(webGLProgram, glVertShader);
  gl.attachShader(webGLProgram, glFragShader);
  // ...
  gl.linkProgram(webGLProgram);

  return shader;
}

綁定 attribute 類型的變量 (但此時還沒傳入 Buffer 數(shù)據(jù),只是設(shè)置了如何訪問等操作);

綁定 uniform 類型的變量。

之后在 app.renderer.shader.bind 方法內(nèi)執(zhí)行下面代碼,應(yīng)用剛剛創(chuàng)建的程序?qū)ο蟆?/p>

this.gl.useProgram(glProgram.program);

渲染階段

前面做的是準(zhǔn)備工作,編譯著色器。

接下來就是渲染階段。

PIXI.Ticker 定時器會在渲染下一幀前調(diào)用 renderer.render 方法,進入 WebGL 的渲染流程。

清空畫布填充背景色

首先是清空畫布。

// 入口方法:renderer.renderTexture.clear
class ObjectRendererSystem {
  render(displayObject, options) {
    // ...

    // (1) 清空畫布,并指定顏色
    renderer.renderTexture.clear();

    // ...
  }
}

它會執(zhí)行 clear 方法

class FramebufferSystem {
  clear(r, g, b, a, mask = BUFFER_BITS.COLOR | BUFFER_BITS.DEPTH) {
    const { gl } = this;
    // 背景色 #cc0 轉(zhuǎn)換為 rbga 格式:
    // (0.800000011920929, 0.800000011920929, 0, 1)
    gl.clearColor(r, g, b, a);
    // 清空顏色和深度緩存
    gl.clear(mask);
  }
}

遞歸調(diào)用 render

遞歸圖形樹(app.stage),調(diào)用它們(繼承了 IRenderableObject 接口類型)的 render 方法,它們會拿到 renderer 對象,然后執(zhí)行自己的渲染邏輯。

// app.stage 是 Container 實例
class Container extends DisplayObject {

  render(renderer) {
    // ...
    this._render(renderer); // 真正的渲染邏輯
    for (let i = 0, j = this.children.length; i < j; ++i) {
      this.children[i].render(renderer);
    }
  }
}

對于前文的示例代碼,會分析矩形屬性,構(gòu)建頂點和片元數(shù)據(jù),然后執(zhí)行 WebGL 的繪制 API。

對矩形三角化,構(gòu)建頂點和片元數(shù)據(jù)

先基于 x、y、width、height 計算出矩形的 4 個頂點放到 points。

然后進行三角化。三角化就是將圖形轉(zhuǎn)換為對應(yīng)的三角形的組合。

所謂圖形的渲染,其實就是繪制一個個小的三角形,組成特定的形狀。這些三角形的點,根據(jù)不同圖形(比如矩形和圓形),需要用不同算法去計算出來,然后把數(shù)據(jù)通過 WebGL 命令交給 GPU,讓它幫我們繪制出來。

首先是填充的三角化(對應(yīng)  buildRectangle.triangulate() )。

基于前面的 4 個點得到填充塊的 4 個點,并設(shè)置對應(yīng)的索引值 indices,之后調(diào)用 gl.drawElements() 需要用到。

接著是描邊的三角化(對應(yīng) buildLine())。

下面是繪制描邊的代碼片段:

PixiJS 的計算邏輯很復(fù)雜,這是因為涉及到連接方式、末端樣式的情況。

同樣,也要計算它的頂點、索引、紋理坐標(biāo)。

西瓜哥我將最終的填充和描邊產(chǎn)生的點,做了一下可視化。

用的是 desmos 可視化工具,這里給一下這個可視化鏈接:

https://www.desmos.com/calculator/r3dwqeweu2?lang=zh-CN。

最后計算好的三角化數(shù)據(jù)會保存到 graph 對象的 batches 數(shù)組下(batches 表示要批量處理的意思)。

batch 對象包括頂點坐標(biāo)(vertexData)、顏色(_batchRGB)、索引(indices)和紋理坐標(biāo)(uvs)。

下面是填充色對應(yīng)的數(shù)據(jù):

批量渲染

這里產(chǎn)生了兩個 batch 對象(對應(yīng)填充和描邊),然后遍歷傳給 BatchRender 類的 render 方法。說是 render 方法,其實并不立即 render,而是將 batch 對象的數(shù)據(jù)解讀和保存起來,之后 flush 時才正式將數(shù)據(jù)加到 WebGL 里。

這些屬性會組合拼裝在一個類型數(shù)組里。6 個一組,逐頂點繪制。

傳完后,會調(diào)用 BatchRender 類的 flush 方法,將頂點數(shù)據(jù)和索引數(shù)組通過 gl.bufferData() 進行綁定。

綁定 uniform 值

在 ShaderSystem 類的 syncUniforms 中,會依次設(shè)置好各個 uniform 變量:tint、translationMatrix、uSamplers、projectionMatrix。

class ShaderSystem {
  
  syncUniforms(group, glProgram, syncData) {
    // 生成同步 uniform 的函數(shù)(不同 uniform 的函數(shù)不同)
    const syncFunc = 
        group.syncUniforms[this.shader.program.id] || 
        this.createSyncGroups(group);
    // 同步!
    syncFunc(glProgram.uniformData, group.uniforms, this.renderer, syncData);
  }

  createSyncGroups(group) {
    const id = this.getSignature(group, this.shader.program.uniformData, "u");
    if (!this.cache[id]) {
      this.cache[id] = generateUniformsSync(group, this.shader.program.uniformData);
    }
    group.syncUniforms[this.shader.program.id] = this.cache[id];
    return group.syncUniforms[this.shader.program.id];
  }
  
}

下面是設(shè)置 tint 的方法:

綁定紋理

綁定紋理。

class TextureSystem {
  bind(texture, location = 0) {
    const { gl } = this;
    // 開啟
    gl.activeTexture(gl.TEXTURE0 + location);
    // ...
    gl.bindTexture(texture.target, glTexture.texture);
    // ...
  }
}

因為示例并不繪制圖片,PixiJS 會提供默認(rèn)的的白色紋理對象(所有值都是 1),這樣顏色值和其相乘,結(jié)果還是原來的顏色值。

渲染

最后調(diào)用 drawBatches 進行繪制。

drawBatches() {
  const dcCount = this._dcIndex;
  const { gl, state: stateSystem } = this.renderer;
  const drawCalls = _BatchRenderer._drawCallPool;
  let curTexArray = null;
  for (let i = 0; i < dcCount; i++) {
    const { texArray, type, size, start, blend } = drawCalls[i];
    if (curTexArray !== texArray) {
      curTexArray = texArray;
      // 剛剛提到的紋理綁定邏輯
      this.bindAndClearTexArray(texArray);
    }
    this.state.blendMode = blend;
    stateSystem.set(this.state);
    // 繪制 API
    gl.drawElements(type, size, gl.UNSIGNED_SHORT, start * 2);
  }
}

最后我們就繪制出一個有填充和描邊的矩形了。

之后 Ticker 會不斷地在繪制下一幀時調(diào)用 renderer 的 render 方法進行渲染,如果圖形沒改變(比如通過 dirtyId 和 cacheDirty 是否相同判斷),我們會跳過三角化的環(huán)節(jié),使用緩存好的數(shù)據(jù)去繪制渲染。

結(jié)尾

PixiJS 繪制圖形使用了 WebGL,為了利用 GPU 的并行能力,需要給著色器一次性提供盡可能多的頂點和顏色信息。

PixiJS 提供了一些基礎(chǔ)圖形,比如矩形。繪制時會根據(jù)圖形屬性信息進行三角化,最后將所有的信息組合起來,一次性提供給 WebGL。

這篇文章其實斷斷續(xù)續(xù)寫了好久,PixiJS 里的彎彎道道挺多的,經(jīng)常調(diào)試了半天就是找不著北了,一度擱置。最后還是硬著頭皮不斷地調(diào)試和思考,總算把這篇文章結(jié)束掉了。

責(zé)任編輯:姜華 來源: 前端西瓜哥
相關(guān)推薦

2023-06-07 08:13:46

PixiJSCanvas 庫

2023-06-08 08:16:33

TickerPixiJS

2023-10-13 07:29:23

PixiJSRunner

2023-03-02 07:44:39

pixijsWebGL

2023-02-22 09:27:31

CanvasWebGL

2022-12-12 09:01:13

2010-08-23 10:17:20

配置DHCP

2011-01-20 10:03:42

PostfixAdmi

2013-09-26 14:09:31

iOS開發(fā)OpenGL ES教程繪制矩形

2024-09-06 09:37:45

WebApp類加載器Web 應(yīng)用

2012-05-31 09:19:22

HTML5

2009-09-24 17:11:53

Hibernate處理

2025-01-24 08:34:29

pixijs圖形設(shè)置光標(biāo)cursor

2023-02-23 08:40:09

Pixijs修改圖形屬性

2022-12-18 22:11:46

2015-06-15 10:32:44

Java核心源碼解讀

2024-10-28 08:15:32

2010-06-09 09:53:44

UML活動圖

2024-10-23 09:05:07

PixijsMatrixTransform

2021-07-25 21:13:50

框架Angular開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號