Maison > interface Web > js tutoriel > Triangles sur le Web Chraw quelque chose

Triangles sur le Web Chraw quelque chose

Patricia Arquette
Libérer: 2024-11-30 02:42:10
original
148 Les gens l'ont consulté

Cette série présente le WebGPU et l'infographie en général.

Regardons d'abord ce que nous allons construire,

Jeu de la vie

Triangles On Web Chraw Something

Rendu 3D

Triangles On Web Chraw Something

Rendu 3D, mais avec éclairage

Triangles On Web Chraw Something

Rendu du modèle 3D

Triangles On Web Chraw Something

Sauf les connaissances de base de JS, aucune connaissance préalable n'est nécessaire.

Le tutoriel est déjà terminé sur mon github, ainsi que le code source.

WebGPU est une API relativement nouvelle pour le GPU. Bien que nommé WebGPU, il peut en fait être considéré comme une couche au-dessus de Vulkan, DirectX 12 et Metal, OpenGL et WebGL. Il est conçu pour être une API de bas niveau et est destiné à être utilisé pour des applications hautes performances, telles que des jeux et des simulations.

Dans ce chapitre, nous allons dessiner quelque chose sur l'écran. La première partie fera référence au didacticiel Google Codelabs. Nous allons créer un jeu de vie sur l'écran.

Point de départ

Nous allons simplement créer un projet Vanilla JS vide rapidement avec TypeScript activé. Effacez ensuite tous les codes supplémentaires, ne laissant que le main.ts.

const main = async () => {
    console.log('Hello, world!')
}

main()
Copier après la connexion
Copier après la connexion
Copier après la connexion

Avant le codage proprement dit, veuillez vérifier si WebGPU est activé sur votre navigateur. Vous pouvez le vérifier sur les exemples WebGPU.

Chrome est désormais activé par défaut. Sur Safari, vous devez accéder aux paramètres du développeur, signaler les paramètres et activer WebGPU.

Nous devons également activer les types pour WebGPU, installer @webgpu/types et dans les options du compilateur tsc, ajouter des "types": ["@webgpu/types"].

De plus, nous remplaçons le

Dessiner un triangle

Il existe de nombreux codes passe-partout pour WebGPU, voici à quoi cela ressemble.

Appareil demandeur

Nous devons d’abord accéder au GPU. Dans WebGPU, cela se fait par le concept d'adaptateur, qui est un pont entre le GPU et le navigateur.

const adapter = await navigator.gpu.requestAdapter();
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ensuite, nous devons demander un appareil à l'adaptateur.

const device = await adapter.requestDevice();
console.log(device);
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Configurer le canevas

On dessine notre triangle sur la toile. Nous devons récupérer l'élément canvas et le configurer.

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, nous utilisons getContext pour obtenir des informations relatives sur le canevas. En spécifiant webgpu, nous obtiendrons un contexte responsable du rendu avec WebGPU.

CanvasFormat est en fait le mode couleur, par exemple srgb. Nous utilisons généralement simplement le format préféré.

Enfin, nous configurons le contexte avec le périphérique et le format.

Comprendre le pipeline de rendu GPU

Avant de plonger plus profondément dans les détails d'ingénierie, nous devons d'abord comprendre comment le GPU gère le rendu.

Le pipeline de rendu GPU est une série d'étapes que le GPU suit pour restituer une image.

L'application exécutée sur GPU est appelée un shader. Le shader est un programme qui s'exécute sur le GPU. Le shader a un langage de programmation spécial dont nous parlerons plus tard.

Le pipeline de rendu comporte les étapes suivantes,

  1. Le CPU charge les données dans le GPU. Le processeur peut supprimer certains objets invisibles pour économiser les ressources du GPU.
  2. Le CPU définit toutes les couleurs, textures et autres données dont le GPU a besoin pour restituer la scène.
  3. Le CPU déclenche un appel de tirage au GPU.
  4. Le GPU récupère les données du CPU et commence le rendu de la scène.
  5. Le GPU s'exécute dans le processus de géométrie, qui traite les sommets de la scène.
  6. Dans le processus de géométrie, la première étape est le vertex shader, qui traite les sommets de la scène. Il peut transformer les sommets, changer la couleur des sommets ou faire d'autres choses sur les sommets.
  7. L'étape suivante est le shader de tessellation, qui traite les sommets de la scène. Il effectue une subdivision des sommets, dont le but est d'augmenter le détail de la scène. Il comporte également de nombreuses procédures mais c'est trop complexe à expliquer ici.
  8. L'étape suivante est le shader géométrique, qui traite les sommets de la scène. Contrairement au vertex shader, où le développeur ne pouvait définir que la manière dont un seul sommet est transformé, le géométrie shader peut définir la manière dont plusieurs sommets sont transformés. Il peut également créer de nouveaux sommets, qui peuvent être utilisés pour créer une nouvelle géométrie.
  9. La dernière étape du processus de géométrie consiste à découper, à supprimer les parties inutiles qui dépassent l'écran, et à éliminer, à supprimer les parties invisibles qui ne sont pas visibles par la caméra.
  10. L'étape suivante est le processus de rastérisation, qui convertit les sommets en fragments. Un fragment est un pixel qui va être restitué à l'écran.
  11. L'étape suivante est l'itération des triangles, qui parcourt les triangles de la scène.
  12. L'étape suivante est le fragment shader, qui traite les fragments de la scène. Cela peut changer la couleur des fragments, changer la texture des fragments ou faire d'autres choses sur les fragments. Dans cette partie, le test de profondeur et le test au pochoir sont également effectués. Le test de profondeur signifie conférer à chaque fragment la valeur de profondeur, et le fragment avec la valeur de profondeur la plus petite sera rendu. Le test du pochoir signifie conférer à chaque fragment la valeur du pochoir, et le fragment qui réussit le test du pochoir sera rendu. La valeur du pochoir est décidée par le développeur.
  13. L'étape suivante est le processus de fusion, qui mélange les fragments de la scène. Par exemple, si deux fragments se chevauchent, le processus de fusion mélangera les deux fragments ensemble.
  14. La dernière étape est le processus de sortie, qui envoie les fragments vers la chaîne d'échange. La chaîne d'échange est une chaîne d'images utilisées pour restituer la scène. Pour faire plus simple, c'est un tampon qui contient l'image qui va être affichée à l'écran.

En fonction des primitives, la plus petite unité que le GPU peut restituer, le pipeline peut comporter différentes étapes. En règle générale, nous utilisons des triangles, ce qui signale au GPU de traiter chaque groupe de 3 sommets comme un triangle.

Création d'une passe de rendu

Render Pass est une étape du rendu GPU complet. Lorsqu'une passe de rendu est créée, le GPU commencera le rendu de la scène, et vice versa une fois le rendu terminé.

Pour créer une passe de rendu, nous devons créer un encodeur chargé de compiler la passe de rendu en codes GPU.

const main = async () => {
    console.log('Hello, world!')
}

main()




Ensuite, nous créons une passe de rendu.

const adapter = await navigator.gpu.requestAdapter();
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, nous créons une passe de rendu avec une pièce jointe de couleur. La pièce jointe est un concept GPU qui représente l'image qui va être rendue. Une image peut avoir de nombreux aspects que le GPU doit traiter, et chacun d'eux est une pièce jointe.

Ici, nous n'avons qu'une seule pièce jointe, qui est la pièce jointe couleur. La vue est le panneau sur lequel le GPU effectuera le rendu, ici nous la définissons sur la texture du canevas.

loadOp est l'opération que le GPU effectuera avant la passe de rendu, clear signifie que le GPU effacera d'abord toutes les données précédentes de la dernière image, et storeOp est l'opération que le GPU effectuera après la passe de rendu, store signifie GPU stockera les données dans la texture.

loadOp peut être load, qui préserve les données de la dernière image, ou clear, qui efface les données de la dernière image. storeOp peut être store, qui stocke les données dans la texture, ou throw, qui supprime les données.

Maintenant, appelez simplement pass.end() pour terminer la passe de rendu. Désormais, la commande est enregistrée dans le tampon de commandes du GPU.

Pour obtenir la commande compilée, utilisez le code suivant,

const device = await adapter.requestDevice();
console.log(device);
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et enfin, soumettez la commande à la file d'attente de rendu du GPU.

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Maintenant, vous devriez voir une vilaine toile noire.

Sur la base de nos concepts stéréotypés sur la 3D, nous nous attendrions à ce que l'espace vide soit de couleur bleue. Nous pouvons le faire en définissant la couleur claire.

const encoder = device.createCommandEncoder();
Copier après la connexion

Dessiner un triangle à l'aide d'un shader

Maintenant, nous allons dessiner un triangle sur la toile. Nous utiliserons un shader pour ce faire. Le langage du shader sera wgsl, WebGPU Shading Language.

Maintenant, supposons que nous voulions dessiner un triangle avec les coordonnées suivantes,

const pass = encoder.beginRenderPass({
  colorAttachments: [{
     view: context.getCurrentTexture().createView(),
     loadOp: "clear",
     storeOp: "store",
  }]
});
Copier après la connexion

Comme nous l'avons indiqué précédemment, pour compléter un pipeline de rendu, nous avons besoin d'un vertex shader et d'un fragment shader.

Shader de sommet

Utilisez le code suivant pour créer des modules de shader.

const commandBuffer = encoder.finish();
Copier après la connexion

l'étiquette ici est simplement un nom, destiné au débogage. le code est le code du shader réel.

Vertex shader est une fonction qui prend n'importe quel paramètre et renvoie la position du sommet. Cependant, contrairement à ce à quoi on pourrait s'attendre, le vertex shader renvoie un vecteur à quatre dimensions, et non un vecteur à trois dimensions. La quatrième dimension est la dimension w, utilisée pour la division en perspective. Nous en discuterons plus tard.

Maintenant, vous pouvez simplement considérer un vecteur à quatre dimensions (x, y, z, w) comme un vecteur à trois dimensions (x/w, y/w, z/w).

Cependant, il y a un autre problème : comment transmettre les données au shader et comment extraire les données du shader.

Pour transmettre les données au shader, nous utilisons le vertexBuffer, un tampon qui contient les données des sommets. Nous pouvons créer un tampon avec le code suivant,

const main = async () => {
    console.log('Hello, world!')
}

main()
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, nous créons un tampon d'une taille de 24 octets, 6 flottants, qui est la taille des sommets.

l'utilisation est l'utilisation du tampon, qui est VERTEX pour les données de sommet. GPUBufferUsage.COPY_DST signifie que ce tampon est valide comme destination de copie. Pour tous les tampons dont les données sont écrites par le CPU, nous devons définir cet indicateur.

La carte ici signifie mapper le tampon sur le CPU, ce qui signifie que le CPU peut lire et écrire le tampon. Le démappage signifie démapper le tampon, ce qui signifie que le CPU ne peut plus lire et écrire le tampon, et donc le contenu est disponible pour le GPU.

Maintenant, nous pouvons écrire les données dans le tampon.

const adapter = await navigator.gpu.requestAdapter();
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, nous mappons le tampon sur le CPU et écrivons les données dans le tampon. Ensuite, nous démapper le tampon.

vertexBuffer.getMappedRange() renverra la plage du tampon mappé au CPU. Nous pouvons l'utiliser pour écrire les données dans le tampon.

Cependant, ce ne sont que des données brutes, et le GPU ne sait pas comment les interpréter. Nous devons définir la disposition du tampon.

const device = await adapter.requestDevice();
console.log(device);
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, arrayStride est le nombre d'octets que le GPU doit parcourir dans le tampon lorsqu'il recherche l'entrée suivante. Par exemple, si arrayStride est 8, le GPU sautera 8 octets pour obtenir l'entrée suivante.

Puisqu'ici, nous utilisons float32x2, la foulée est de 8 octets, 4 octets pour chaque flotteur et 2 flotteurs pour chaque sommet.

Maintenant, nous pouvons écrire le vertex shader.

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, @vertex signifie qu'il s'agit d'un vertex shader. @location(0) signifie l'emplacement de l'attribut, qui est 0, tel que défini précédemment. Veuillez noter que dans le langage shader, vous avez affaire à la disposition du tampon, donc chaque fois que vous transmettez une valeur, vous devez transmettre soit une structure dont les champs ont défini @location, soit simplement une valeur avec @location.

vec2f est un vecteur flottant à deux dimensions et vec4f est un vecteur flottant à quatre dimensions. Puisque le vertex shader est requis pour renvoyer une position vec4f, nous devons l'annoter avec @builtin(position).

Shader de fragments

Le Fragment shader, de la même manière, est quelque chose qui prend la sortie du sommet interpolé et affiche les pièces jointes, la couleur dans ce cas. L'interpolation signifie que bien que seuls certains pixels sur les sommets aient une valeur déterminée, pour un pixel sur deux, les valeurs sont interpolées, soit linéairement, moyennées ou par d'autres moyens. La couleur du fragment est un vecteur à quatre dimensions, qui est la couleur du fragment, respectivement rouge, vert, bleu et alpha.

Veuillez noter que la couleur est comprise entre 0 et 1, et non entre 0 et 255. De plus, le fragment shader définit la couleur de chaque sommet, et non la couleur du triangle. La couleur du triangle est déterminée par la couleur des sommets, par interpolation.

Comme nous ne prenons actuellement pas la peine de contrôler la couleur du fragment, nous pouvons simplement renvoyer une couleur constante.

const main = async () => {
    console.log('Hello, world!')
}

main()
Copier après la connexion
Copier après la connexion
Copier après la connexion

Pipeline de rendu

Ensuite, nous définissons le pipeline de rendu personnalisé en remplaçant le vertex et le fragment shader.

const adapter = await navigator.gpu.requestAdapter();
Copier après la connexion
Copier après la connexion
Copier après la connexion

Notez que dans Fragment Shader, nous devons spécifier le format de la cible, qui est le format du canevas.

Appel au tirage au sort

Avant la fin de la passe de rendu, nous ajoutons l'appel de tirage.

const device = await adapter.requestDevice();
console.log(device);
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, dans setVertexBuffer, le premier paramètre est l'index du tampon, dans le champ de définition du pipeline buffers, et le deuxième paramètre est le tampon lui-même.

Lors de l'appel de draw, le paramètre est le nombre de sommets à dessiner. Puisque nous avons 3 sommets, nous en dessinons 3.

Maintenant, vous devriez voir un triangle jaune sur la toile.

Dessiner des cellules de jeu de vie

Maintenant, nous modifions un peu nos codes - puisque nous voulons construire un jeu de vie, nous devons donc dessiner des carrés au lieu de triangles.

Un carré est en fait deux triangles, nous devons donc dessiner 6 sommets. Les changements ici sont simples et vous n'avez pas besoin d'une explication détaillée.

const canvas = document.getElementById('app') as HTMLCanvasElement;
const context = canvas.getContext("webgpu")!;
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
    device: device,
    format: canvasFormat,
});
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Maintenant, vous devriez voir un carré jaune sur la toile.

Système de coordonnées

Nous n'avons pas discuté du système de coordonnées du GPU. C'est plutôt simple. Le système de coordonnées actuel du GPU est un système de coordonnées droitier, ce qui signifie que l'axe des x pointe vers la droite, l'axe des y pointe vers le haut et l'axe z pointe hors de l'écran.

La plage du système de coordonnées est de -1 à 1. L'origine est au centre de l'écran. L'axe z va de 0 à 1, 0 est le plan proche et 1 est le plan éloigné. Cependant, l'axe z correspond à la profondeur. Lorsque vous effectuez un rendu 3D, vous ne pouvez pas simplement utiliser l'axe z pour déterminer la position de l'objet, vous devez également utiliser la division en perspective. C'est ce qu'on appelle la coordonnée normalisée de l'appareil NDC.

Par exemple, si vous souhaitez dessiner un carré dans le coin supérieur gauche de l'écran, les sommets sont (-1, 1), (-1, 0), (0, 1), (0, 0) , mais vous devez utiliser deux triangles pour le dessiner.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal