Sampler Parameters

代碼分析說明:
本案例主要是紋理相關(guān)參數(shù)的展示

import { mat4 } from 'wgpu-matrix';
import { makeSample, SampleInit } from '../../components/SampleLayout';

import texturedSquareWGSL from './texturedSquare.wgsl';
import showTextureWGSL from './flangeSize';
// 所有圖像的變換矩陣
const kMatrices: Readonly<Float32Array> = new Float32Array([
  // Row 1: Scale by 2
  ...mat4.scale(mat4.rotationZ(Math.PI / 16), [2, 2, 1]),
  ...mat4.scale(mat4.identity(), [2, 2, 1]),
  ...mat4.scale(mat4.rotationX(-Math.PI * 0.3), [2, 2, 1]),
  ...mat4.scale(mat4.rotationX(-Math.PI * 0.42), [2, 2, 1]),
  // Row 2: Scale by 1
  ...mat4.rotationZ(Math.PI / 16),
  ...mat4.identity(),
  ...mat4.rotationX(-Math.PI * 0.3),
  ...mat4.rotationX(-Math.PI * 0.42),
  // Row 3: Scale by 0.9
  ...mat4.scale(mat4.rotationZ(Math.PI / 16), [0.9, 0.9, 1]),
  ...mat4.scale(mat4.identity(), [0.9, 0.9, 1]),
  ...mat4.scale(mat4.rotationX(-Math.PI * 0.3), [0.9, 0.9, 1]),
  ...mat4.scale(mat4.rotationX(-Math.PI * 0.42), [0.9, 0.9, 1]),
  // Row 4: Scale by 0.3
  ...mat4.scale(mat4.rotationZ(Math.PI / 16), [0.3, 0.3, 1]),
  ...mat4.scale(mat4.identity(), [0.3, 0.3, 1]),
  ...mat4.scale(mat4.rotationX(-Math.PI * 0.3), [0.3, 0.3, 1]),
]);

const init: SampleInit = async ({ canvas, pageState, gui }) => {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();

  if (!pageState.active) return;

  //
  // GUI controls
  //

  const kInitConfig = {
    flangeLogSize: 1.0,
    highlightFlange: false,
    animation: 0.1,
  } as const;
  const config = { ...kInitConfig };
  const updateConfigBuffer = () => {
    const t = (performance.now() / 1000) * 0.5;
    const data = new Float32Array([
      Math.cos(t) * config.animation,
      Math.sin(t) * config.animation,
      (2 ** config.flangeLogSize - 1) / 2,
      Number(config.highlightFlange),
    ]);
    device.queue.writeBuffer(bufConfig, 64, data);
  };

  const kInitSamplerDescriptor = {
    addressModeU: 'clamp-to-edge',
    addressModeV: 'clamp-to-edge',
    magFilter: 'linear',
    minFilter: 'linear',
    mipmapFilter: 'linear',
    lodMinClamp: 0,
    lodMaxClamp: 4,
    maxAnisotropy: 1,
  } as const;
  const samplerDescriptor: GPUSamplerDescriptor = { ...kInitSamplerDescriptor };

  {
    const buttons = {
      initial() {
        Object.assign(config, kInitConfig);
        Object.assign(samplerDescriptor, kInitSamplerDescriptor);
        gui.updateDisplay();
      },
      checkerboard() {
        Object.assign(config, { flangeLogSize: 10 });
        Object.assign(samplerDescriptor, {
          addressModeU: 'repeat',
          addressModeV: 'repeat',
        });
        gui.updateDisplay();
      },
      smooth() {
        Object.assign(samplerDescriptor, {
          magFilter: 'linear',
          minFilter: 'linear',
          mipmapFilter: 'linear',
        });
        gui.updateDisplay();
      },
      crunchy() {
        Object.assign(samplerDescriptor, {
          magFilter: 'nearest',
          minFilter: 'nearest',
          mipmapFilter: 'nearest',
        });
        gui.updateDisplay();
      },
    };
    const presets = gui.addFolder('Presets');
    presets.open();
    presets.add(buttons, 'initial').name('reset to initial');
    presets.add(buttons, 'checkerboard').name('checkered floor');
    presets.add(buttons, 'smooth').name('smooth (linear)');
    presets.add(buttons, 'crunchy').name('crunchy (nearest)');

    const flangeFold = gui.addFolder('Plane settings');
    flangeFold.open();
    flangeFold.add(config, 'flangeLogSize', 0, 10.0, 0.1).name('size = 2**');
    flangeFold.add(config, 'highlightFlange');
    flangeFold.add(config, 'animation', 0, 0.5);

    gui.width = 280;
    {
      const folder = gui.addFolder('GPUSamplerDescriptor');
      folder.open();

      const kAddressModes = ['clamp-to-edge', 'repeat', 'mirror-repeat'];
      folder.add(samplerDescriptor, 'addressModeU', kAddressModes);
      folder.add(samplerDescriptor, 'addressModeV', kAddressModes);

      const kFilterModes = ['nearest', 'linear'];
      folder.add(samplerDescriptor, 'magFilter', kFilterModes);
      folder.add(samplerDescriptor, 'minFilter', kFilterModes);
      const kMipmapFilterModes = ['nearest', 'linear'] as const;
      folder.add(samplerDescriptor, 'mipmapFilter', kMipmapFilterModes);

      const ctlMin = folder.add(samplerDescriptor, 'lodMinClamp', 0, 4, 0.1);
      const ctlMax = folder.add(samplerDescriptor, 'lodMaxClamp', 0, 4, 0.1);
      ctlMin.onChange((value: number) => {
        if (samplerDescriptor.lodMaxClamp < value) ctlMax.setValue(value);
      });
      ctlMax.onChange((value: number) => {
        if (samplerDescriptor.lodMinClamp > value) ctlMin.setValue(value);
      });

      {
        const folder2 = folder.addFolder(
          'maxAnisotropy (set only if all "linear")'
        );
        folder2.open();
        const kMaxAnisotropy = 16;
        folder2.add(samplerDescriptor, 'maxAnisotropy', 1, kMaxAnisotropy, 1);
      }
    }
  }

  //
  // Canvas setup
  //

  // Low-res, pixelated render target so it's easier to see fine details.
  const kCanvasSize = 200;
  const kViewportGridSize = 4;
  const kViewportGridStride = Math.floor(kCanvasSize / kViewportGridSize);
  const kViewportSize = kViewportGridStride - 2;

  // The canvas buffer size is 200x200.
  // Compute a canvas CSS size such that there's an integer number of device
  // pixels per canvas pixel ("integer" or "pixel-perfect" scaling).
  // Note the result may be 1 pixel off since ResizeObserver is not used.
  const kCanvasLayoutCSSSize = 600; // set by template styles
  const kCanvasLayoutDevicePixels = kCanvasLayoutCSSSize * devicePixelRatio;
  const kScaleFactor = Math.floor(kCanvasLayoutDevicePixels / kCanvasSize);
  const kCanvasDevicePixels = kScaleFactor * kCanvasSize;
  const kCanvasCSSSize = kCanvasDevicePixels / devicePixelRatio;
  canvas.style.imageRendering = 'pixelated';
  canvas.width = canvas.height = kCanvasSize;
  canvas.style.minWidth = canvas.style.maxWidth = kCanvasCSSSize + 'px';
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  const context = canvas.getContext('webgpu') as GPUCanvasContext;
  context.configure({
    device,
    format: presentationFormat,
    alphaMode: 'premultiplied',
  });

  //
  // Initialize test texture
  //

  // Set up a texture with 4 mip levels, each containing a differently-colored
  // checkerboard with 1x1 pixels (so when rendered the checkerboards are
  // different sizes). This is different from a normal mipmap where each level
  // would look like a lower-resolution version of the previous one.
  // Level 0 is 16x16 white/black
  // Level 1 is 8x8 blue/black
  // Level 2 is 4x4 yellow/black
  // Level 3 is 2x2 pink/black
  const kTextureMipLevels = 4;
  const kTextureBaseSize = 16;
  const checkerboard = device.createTexture({
    format: 'rgba8unorm',
    usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
    size: [kTextureBaseSize, kTextureBaseSize],
    mipLevelCount: 4,
  });
  const checkerboardView = checkerboard.createView();

  const kColorForLevel = [
    [255, 255, 255, 255],
    [30, 136, 229, 255], // blue
    [255, 193, 7, 255], // yellow
    [216, 27, 96, 255], // pink
  ];
  for (let mipLevel = 0; mipLevel < kTextureMipLevels; ++mipLevel) {
    const size = 2 ** (kTextureMipLevels - mipLevel); // 16, 8, 4, 2
    const data = new Uint8Array(size * size * 4);
    for (let y = 0; y < size; ++y) {
      for (let x = 0; x < size; ++x) {
        data.set(
          (x + y) % 2 ? kColorForLevel[mipLevel] : [0, 0, 0, 255],
          (y * size + x) * 4
        );
      }
    }
    device.queue.writeTexture(
      { texture: checkerboard, mipLevel },
      data,
      { bytesPerRow: size * 4 },
      [size, size]
    );
  }

  //
  // "Debug" view of the actual texture contents
  //

  const showTextureModule = device.createShaderModule({
    code: showTextureWGSL,
  });
  const showTexturePipeline = device.createRenderPipeline({
    layout: 'auto',
    vertex: { module: showTextureModule, entryPoint: 'vmain' },
    fragment: {
      module: showTextureModule,
      entryPoint: 'fmain',
      targets: [{ format: presentationFormat }],
    },
    primitive: { topology: 'triangle-list' },
  });

  const showTextureBG = device.createBindGroup({
    layout: showTexturePipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: checkerboardView }],
  });

  //
  // Pipeline for drawing the test squares
  //

  const texturedSquareModule = device.createShaderModule({
    code: texturedSquareWGSL,
  });

  const texturedSquarePipeline = device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: texturedSquareModule,
      entryPoint: 'vmain',
      constants: { kTextureBaseSize, kViewportSize },
    },
    fragment: {
      module: texturedSquareModule,
      entryPoint: 'fmain',
      targets: [{ format: presentationFormat }],
    },
    primitive: { topology: 'triangle-list' },
  });
  const texturedSquareBGL = texturedSquarePipeline.getBindGroupLayout(0);

  const bufConfig = device.createBuffer({
    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
    size: 128,
  });
  // View-projection matrix set up so it doesn't transform anything at z=0.
  const kCameraDist = 3;
  const viewProj = mat4.translate(
    mat4.perspective(2 * Math.atan(1 / kCameraDist), 1, 0.1, 100),
    [0, 0, -kCameraDist]
  );
  device.queue.writeBuffer(bufConfig, 0, viewProj as Float32Array);

  const bufMatrices = device.createBuffer({
    usage: GPUBufferUsage.STORAGE,
    size: kMatrices.byteLength,
    mappedAtCreation: true,
  });
  new Float32Array(bufMatrices.getMappedRange()).set(kMatrices);
  bufMatrices.unmap();

  function frame() {
    // Sample is no longer the active page.
    if (!pageState.active) return;

    updateConfigBuffer();

    const sampler = device.createSampler({
      ...samplerDescriptor,
      maxAnisotropy:
        samplerDescriptor.minFilter === 'linear' &&
        samplerDescriptor.magFilter === 'linear' &&
        samplerDescriptor.mipmapFilter === 'linear'
          ? samplerDescriptor.maxAnisotropy
          : 1,
    });

    const bindGroup = device.createBindGroup({
      layout: texturedSquareBGL,
      entries: [
        { binding: 0, resource: { buffer: bufConfig } },
        { binding: 1, resource: { buffer: bufMatrices } },
        { binding: 2, resource: sampler },
        { binding: 3, resource: checkerboardView },
      ],
    });

    const textureView = context.getCurrentTexture().createView();

    const commandEncoder = device.createCommandEncoder();

    const renderPassDescriptor: GPURenderPassDescriptor = {
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r: 0.2, g: 0.2, b: 0.2, a: 1.0 },
          loadOp: 'clear',
          storeOp: 'store',
        },
      ],
    };

    const pass = commandEncoder.beginRenderPass(renderPassDescriptor);
    // Draw test squares
    pass.setPipeline(texturedSquarePipeline);
    pass.setBindGroup(0, bindGroup);
    for (let i = 0; i < kViewportGridSize ** 2 - 1; ++i) {
      const vpX = kViewportGridStride * (i % kViewportGridSize) + 1;
      const vpY = kViewportGridStride * Math.floor(i / kViewportGridSize) + 1;
      pass.setViewport(vpX, vpY, kViewportSize, kViewportSize, 0, 1);
       // i 是第一個實例號
      pass.draw(6, 1, 0, i);
    }
    // Show texture contents
    pass.setPipeline(showTexturePipeline);
    pass.setBindGroup(0, showTextureBG);
    const kLastViewport = (kViewportGridSize - 1) * kViewportGridStride + 1;
  /**
   * 將光柵化階段使用的視口設(shè)置為從標準化設(shè)備坐標線性映射到視口坐標
   * @param x 視口的最小X值(以像素為單位)
   * @param y 視口的最小Y值(以像素為單位)
   * @param width 口的寬度(以像素為單位)
   * @param height 視口的高度(以像素為單位)
   * @param minDepth 視口的最小深度值
   * @param maxDepth 視口的最大深度值
   */
  //setViewport(
   // x: FLOAT,
 //   y: FLOAT,
 //   width: FLOAT,
 //   height: FLOAT,
 //   minDepth: FLOAT,
 //   maxDepth: FLOAT
 // ): void;
    pass.setViewport(kLastViewport, kLastViewport, 32, 32, 0, 1);
    pass.draw(6, 1, 0, 0);
    pass.setViewport(kLastViewport + 32, kLastViewport, 16, 16, 0, 1);
    pass.draw(6, 1, 0, 1);
    pass.setViewport(kLastViewport + 32, kLastViewport + 16, 8, 8, 0, 1);
    pass.draw(6, 1, 0, 2);
    pass.setViewport(kLastViewport + 32, kLastViewport + 24, 4, 4, 0, 1);
    pass.draw(6, 1, 0, 3);
    pass.end();

    device.queue.submit([commandEncoder.finish()]);
    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
};

texturedSquare.wgsl

struct Config {
  viewProj: mat4x4f,
  animationOffset: vec2f,
  flangeSize: f32,
  highlightFlange: f32,
};
@group(0) @binding(0) var<uniform> config: Config;
@group(0) @binding(1) var<storage, read> matrices: array<mat4x4f>;
@group(0) @binding(2) var samp: sampler;
@group(0) @binding(3) var tex: texture_2d<f32>;

struct Varying {
  @builtin(position) pos: vec4f,
  @location(0) uv: vec2f,
}

override kTextureBaseSize: f32;
override kViewportSize: f32;

@vertex
fn vmain(
  @builtin(instance_index) instance_index: u32,
  @builtin(vertex_index) vertex_index: u32,
) -> Varying {
  let flange = config.flangeSize;
// UV 通過flangeSize來顯示重復的次數(shù)
  var uvs = array(
    vec2(-flange, -flange), vec2(-flange, 1 + flange), vec2(1 + flange, -flange),
    vec2(1 + flange, -flange), vec2(-flange, 1 + flange), vec2(1 + flange, 1 + flange),
  );
  // Default size (if matrix is the identity) makes 1 texel = 1 pixel.
  let radius = (1 + 2 * flange) * kTextureBaseSize / kViewportSize;
 //  計算頂點
  var positions = array(
    vec2(-radius, -radius), vec2(-radius, radius), vec2(radius, -radius),
    vec2(radius, -radius), vec2(-radius, radius), vec2(radius, radius),
  );
  
 // 獲取不同的矩陣 
  let modelMatrix = matrices[instance_index];
  let pos = config.viewProj * modelMatrix * vec4f(positions[vertex_index] + config.animationOffset, 0, 1);
  return Varying(pos, uvs[vertex_index]);
}

@fragment
fn fmain(vary: Varying) -> @location(0) vec4f {
  let uv = vary.uv;
  var color = textureSample(tex, samp, uv);
 // UV 不在【0-1】之間的添加紅色到紋理上
  let outOfBounds = uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1;
  if config.highlightFlange > 0 && outOfBounds {
    color += vec4(0.7, 0, 0, 0);
  }

  return color;
}

texturedSquare.wgsl

@group(0) @binding(0) var tex: texture_2d<f32>;

struct Varying {
  @builtin(position) pos: vec4f,
  @location(0) texelCoord: vec2f,
  @location(1) mipLevel: f32,
}

const kMipLevels = 4;
const baseMipSize: u32 = 16;

@vertex
fn vmain(
  @builtin(instance_index) instance_index: u32, // used as mipLevel
  @builtin(vertex_index) vertex_index: u32,
) -> Varying {
// 四邊形的UV坐標
  var square = array(
    vec2f(0, 0), vec2f(0, 1), vec2f(1, 0),
    vec2f(1, 0), vec2f(0, 1), vec2f(1, 1),
  );
// 獲取UV
  let uv = square[vertex_index];
 // UV 轉(zhuǎn)換為頂點
  let pos = vec4(uv * 2 - vec2(1, 1), 0.0, 1.0);
 // 實例ID
  let mipLevel = instance_index;
 // 不同的實例不同的大小 
  let mipSize = f32(1 << (kMipLevels - mipLevel));
 // 縮放UV
  let texelCoord = uv * mipSize;
  return Varying(pos, texelCoord, f32(mipLevel));
}

@fragment
fn fmain(vary: Varying) -> @location(0) vec4f {
 // 加載不同等級的 texture
  return textureLoad(tex, vec2u(vary.texelCoord), u32(vary.mipLevel));
}

總結(jié)步驟

  1. 該案例相對比較復雜躲撰,后續(xù)在繼續(xù)研究针贬,本次不做深入的解釋
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拢蛋,隨后出現(xiàn)的幾起案子桦他,更是在濱河造成了極大的恐慌,老刑警劉巖瓤狐,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞬铸,死亡現(xiàn)場離奇詭異,居然都是意外死亡础锐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門荧缘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來皆警,“玉大人,你說我怎么就攤上這事截粗⌒判眨” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵绸罗,是天一觀的道長意推。 經(jīng)常有香客問我,道長珊蟀,這世上最難降的妖魔是什么菊值? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上腻窒,老公的妹妹穿的比我還像新娘昵宇。我一直安慰自己,他們只是感情好儿子,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布瓦哎。 她就那樣靜靜地躺著,像睡著了一般柔逼。 火紅的嫁衣襯著肌膚如雪蒋譬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天愉适,我揣著相機與錄音犯助,去河邊找鬼。 笑死儡毕,一個胖子當著我的面吹牛也切,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腰湾,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼雷恃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了费坊?” 一聲冷哼從身側(cè)響起倒槐,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎附井,沒想到半個月后讨越,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡永毅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年把跨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沼死。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡着逐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出意蛀,到底是詐尸還是另有隱情耸别,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布县钥,位于F島的核電站秀姐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏若贮。R本人自食惡果不足惜省有,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一痒留、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锥咸,春花似錦狭瞎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雪侥,卻和暖如春碗殷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背速缨。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工锌妻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旬牲。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓仿粹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親原茅。 傳聞我的和親對象是個殘疾皇子吭历,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容