Měsíc WebGL. Den 28. Detekce kliknutí. Část II

Toto je série blogových příspěvků souvisejících s WebGL. Nový příspěvek bude k dispozici každý den


Připojte se do seznamu adresátů a získejte nové příspěvky přímo do vaší doručené pošty

Zdrojový kód je k dispozici zde

Postaveno s

Ahoj 👋

Vítejte v měsíci WebGL

Včera jsme vykreslili náš terén minecraftu do textury mimo obrazovku, kde je každý objekt zakódován do určité barvy a naučili jsme se, jak číst barvy pixelů z textury zpět do JS. Nyní dekódujeme tuto barvu na index objektu a zvýrazníme vybranou kostku

gl.readPixels vyplní Uint8Array s barvami pixelů začínajícími v levém dolním rohu. Potřebujeme převést souřadnice klienta na souřadnice pixelů v poli. Nezapomeňte na poměr pixelů, protože náš framebuffer mimo obrazovku ho bere v úvahu a souřadnice události ne.

📄 src/minecraft.js

      requestAnimationFrame(render);
  }

- document.body.addEventListener('click', () => {
+ document.body.addEventListener('click', (e) => {
      coloredCubesRenderBuffer.bind(gl);

      renderTerrain(gl, viewMatrix, projectionMatrix, true);

      const pixels = new Uint8Array(canvas.width * canvas.height * 4);
      gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ 
+     const x = e.clientX * devicePixelRatio;
+     const y = (canvas.offsetHeight - e.clientY) * devicePixelRatio;
  });

  (async () => {

Musíme přeskočit y řádků (y * canvas.width ) vynásobené 4 (4 celá čísla na pixel)

📄 src/minecraft.js


      const x = e.clientX * devicePixelRatio;
      const y = (canvas.offsetHeight - e.clientY) * devicePixelRatio;
+ 
+     const rowsToSkip = y * canvas.width * 4;
  });

  (async () => {

Vodorovná souřadnice je x * 4 (souřadnice vynásobená počtem celých čísel na pixel)

📄 src/minecraft.js

      const y = (canvas.offsetHeight - e.clientY) * devicePixelRatio;

      const rowsToSkip = y * canvas.width * 4;
+     const col = x * 4;
  });

  (async () => {

Takže konečný index pixelu je rowsToSkip + col

📄 src/minecraft.js


      const rowsToSkip = y * canvas.width * 4;
      const col = x * 4;
+ 
+     const pixelIndex = rowsToSkip + col;
  });

  (async () => {

Nyní musíme přečíst každou barevnou složku pixelu

📄 src/minecraft.js

      const col = x * 4;

      const pixelIndex = rowsToSkip + col;
+ 
+     const r = pixels[pixelIndex];
+     const g = pixels[pixelIndex + 1];
+     const b = pixels[pixelIndex + 2];
+     const a = pixels[pixelIndex + 3];
  });

  (async () => {

Nyní musíme převést zpět na celé číslo z r g b

📄 src/minecraft.js

      requestAnimationFrame(render);
  }

+ function rgbToInt(r, g, b) {
+     return b + g * 255 + r * 255 ** 2;
+ }
+ 
  document.body.addEventListener('click', (e) => {
      coloredCubesRenderBuffer.bind(gl);


Pojďme vypustit kód otáčení kamery, aby byla scéna statická

📄 src/minecraft.js

  function render() {
      offscreenRenderBuffer.clear(gl);

-     mat4.translate(cameraFocusPointMatrix, cameraFocusPointMatrix, [0, 0, -30]);
-     mat4.rotateY(cameraFocusPointMatrix, cameraFocusPointMatrix, Math.PI / 360);
-     mat4.translate(cameraFocusPointMatrix, cameraFocusPointMatrix, [0, 0, 30]);
- 
-     mat4.getTranslation(cameraFocusPoint, cameraFocusPointMatrix);
- 
      mat4.lookAt(viewMatrix, cameraPosition, cameraFocusPoint, [0, 1, 0]);

      renderSkybox(gl, viewMatrix, projectionMatrix);
      const g = pixels[pixelIndex + 1];
      const b = pixels[pixelIndex + 2];
      const a = pixels[pixelIndex + 3];
+ 
+     const index = rgbToInt(r, g, b);
+ 
+     console.log(index);
  });

  (async () => {

a aktualizujte počáteční polohu kamery, abyste viděli scénu lépe

📄 src/minecraft.js


  gl.viewport(0, 0, canvas.width, canvas.height);

- const cameraPosition = [0, 5, 0];
- const cameraFocusPoint = vec3.fromValues(0, 0, 30);
+ const cameraPosition = [0, 10, 0];
+ const cameraFocusPoint = vec3.fromValues(30, 0, 30);
  const cameraFocusPointMatrix = mat4.create();

  mat4.fromTranslation(cameraFocusPointMatrix, cameraFocusPoint);

Dále předáme vybraný index barev do vertex shaderu jako proměnlivý

📄 src/shaders/3d-textured.v.glsl


  uniform mat4 viewMatrix;
  uniform mat4 projectionMatrix;
+ uniform float selectedObjectIndex;

  varying vec2 vTexCoord;
  varying vec3 vColor;

A vynásobte barvu objektu, pokud jeho index odpovídá vybranému indexu objektu

📄 src/shaders/3d-textured.f.glsl

  varying vec3 vColor;

  uniform float renderIndices;
+ varying vec4 vColorMultiplier;

  void main() {
-     gl_FragColor = texture2D(texture, vTexCoord * vec2(1, -1) + vec2(0, 1));
+     gl_FragColor = texture2D(texture, vTexCoord * vec2(1, -1) + vec2(0, 1)) * vColorMultiplier;

      if (renderIndices == 1.0) {
          gl_FragColor.rgb = vColor;

📄 src/shaders/3d-textured.v.glsl


  varying vec2 vTexCoord;
  varying vec3 vColor;
+ varying vec4 vColorMultiplier;

  vec3 encodeObject(float id) {
      int b = int(mod(id, 255.0));

      vTexCoord = texCoord;
      vColor = encodeObject(index);
+     
+     if (selectedObjectIndex == index) {
+         vColorMultiplier = vec4(1.5, 1.5, 1.5, 1.0);
+     } else {
+         vColorMultiplier = vec4(1.0, 1.0, 1.0, 1.0);
+     }
  }

a odrážet změny shaderu v js

📄 src/minecraft-terrain.js

      State.ext.vertexAttribDivisorANGLE(State.programInfo.attributeLocations.index, 0);
  }

- export function render(gl, viewMatrix, projectionMatrix, renderIndices) {
+ export function render(gl, viewMatrix, projectionMatrix, renderIndices, selectedObjectIndex) {
      gl.useProgram(State.program);

      setupAttributes(gl);
      gl.uniformMatrix4fv(State.programInfo.uniformLocations.viewMatrix, false, viewMatrix);
      gl.uniformMatrix4fv(State.programInfo.uniformLocations.projectionMatrix, false, projectionMatrix);

+     gl.uniform1f(State.programInfo.uniformLocations.selectedObjectIndex, selectedObjectIndex);
+ 
      if (renderIndices) {
          gl.uniform1f(State.programInfo.uniformLocations.renderIndices, 1);
      } else {

📄 src/minecraft.js


  gl.uniform2f(programInfo.uniformLocations.resolution, canvas.width, canvas.height);

+ let selectedObjectIndex = -1;
+ 
  function render() {
      offscreenRenderBuffer.clear(gl);

      mat4.lookAt(viewMatrix, cameraPosition, cameraFocusPoint, [0, 1, 0]);

      renderSkybox(gl, viewMatrix, projectionMatrix);
-     renderTerrain(gl, viewMatrix, projectionMatrix);
+     renderTerrain(gl, viewMatrix, projectionMatrix, false, selectedObjectIndex);

      gl.useProgram(program);


      const index = rgbToInt(r, g, b);

-     console.log(index);
+     selectedObjectIndex = index;
  });

  (async () => {

A je to! Nyní známe vybraný index objektu, takže můžeme provádět operace JS i vizuální zpětnou vazbu!

Děkujeme za přečtení!


Připojte se do seznamu adresátů a získejte nové příspěvky přímo do vaší doručené pošty

Zdrojový kód je k dispozici zde

Postaveno s