成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

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

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

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

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

PixiJS 版本為 7.2.4。

要求讀者熟悉 WebGL 的基礎知識。

本文會 以繪制設置了填充和描邊的矩形為例子,看底層 WebGL 的調用執行。

業務層代碼:

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);

繪制結果為:

創建 gl

第一步是創建 gl 對象,上下文類型優先使用 "webgl2"。

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

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

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

構建著色器代碼片段

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

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

著色器模板

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

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

batch 文件夾都是和 批量繪制 有關的邏輯,批量、減少 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;
}

片元著色器和顏色有關。

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 初始化時,上面的模板會進行一系列的改造,兩個著色器最終轉換為下面的樣子。

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

補充一些簡單注釋說明。

頂點著色器

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

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

varying vec2 vTextureCoord; // varing 都是用來傳遞的
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;
}

片元著色器

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

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

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

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

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

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

編譯著色器程序

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

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

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

  // 創建程序對象
  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]);
  }

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

  //(3)綁定 uniformLocation(準確來說是拿地址,還沒正式綁定)
  // 屬性(對應 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;
}

分成三個主要步驟。

創建著色器對象、程序對象。

compileShader 實現:

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 數據,只是設置了如何訪問等操作);

綁定 uniform 類型的變量。

之后在 app.renderer.shader.bind 方法內執行下面代碼,應用剛剛創建的程序對象。

this.gl.useProgram(glProgram.program);

渲染階段

前面做的是準備工作,編譯著色器。

接下來就是渲染階段。

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

清空畫布填充背景色

首先是清空畫布。

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

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

    // ...
  }
}

它會執行 clear 方法

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

遞歸調用 render

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

// 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);
    }
  }
}

對于前文的示例代碼,會分析矩形屬性,構建頂點和片元數據,然后執行 WebGL 的繪制 API。

對矩形三角化,構建頂點和片元數據

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

然后進行三角化。三角化就是將圖形轉換為對應的三角形的組合。

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

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

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

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

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

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

同樣,也要計算它的頂點、索引、紋理坐標。

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

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

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

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

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

下面是填充色對應的數據:

批量渲染

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

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

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

綁定 uniform 值

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

class ShaderSystem {
  
  syncUniforms(group, glProgram, syncData) {
    // 生成同步 uniform 的函數(不同 uniform 的函數不同)
    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];
  }
  
}

下面是設置 tint 的方法:

綁定紋理

綁定紋理。

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

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

渲染

最后調用 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 會不斷地在繪制下一幀時調用 renderer 的 render 方法進行渲染,如果圖形沒改變(比如通過 dirtyId 和 cacheDirty 是否相同判斷),我們會跳過三角化的環節,使用緩存好的數據去繪制渲染。

結尾

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

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

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

責任編輯:姜華 來源: 前端西瓜哥
相關推薦

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

2025-06-23 00:03:00

2010-08-23 10:17:20

配置DHCP

2011-01-20 10:03:42

PostfixAdmi

2024-09-06 09:37:45

WebApp類加載器Web 應用

2013-09-26 14:09:31

iOS開發OpenGL ES教程繪制矩形

2012-05-31 09:19:22

HTML5

2009-09-24 17:11:53

Hibernate處理

2024-10-23 09:05:07

PixijsMatrixTransform

2022-12-18 22:11:46

2023-02-23 08:40:09

Pixijs修改圖形屬性

2010-06-09 09:53:44

UML活動圖

2025-01-24 08:34:29

pixijs圖形設置光標cursor

2021-07-25 21:13:50

框架Angular開發

2015-06-15 10:32:44

Java核心源碼解讀
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久久国产一区二区三区 | 一区二区国产在线观看 | 欧美日韩一二三区 | 欧美亚州综合 | 91免费在线 | 99riav3国产精品视频 | 久久国 | www.狠狠操 | 国产一区 日韩 | 国产在线精品一区二区 | 第一区在线观看免费国语入口 | 亚洲视频在线一区 | 久久久精品网站 | a级网站| 97国产成人 | 天天色天天射天天干 | www.一区二区三区.com | 在线免费亚洲视频 | 欧美一区2区三区4区公司 | 亚洲精品久久久久久下一站 | 亚洲日日 | 伊人色综合久久天天五月婷 | 国产性生活一级片 | 99福利视频 | 91黄在线观看 | 成人国产在线观看 | 久久99久久99精品免视看婷婷 | 在线观看视频中文字幕 | 久久中文字幕视频 | 国产欧美日韩在线 | 久久精品日产第一区二区三区 | 亚洲激精日韩激精欧美精品 | 欧州一区二区三区 | 国家aaa的一级看片 h片在线看 | 五十女人一级毛片 | 欧美在线视频一区二区 | 亚洲美女网站 | 亚洲人人 | 久久久激情视频 | 国产丝袜人妖cd露出 | 91麻豆精品国产91久久久更新资源速度超快 |