Bonjour à tous! Aujourd'hui, je continue à discuter . Cet article sera deux fois plus gros que le précédent. Tiens bon ! du rendu dans Unity Qu'est-ce qu'un shader ? Basé sur ce qui a été décrit dans un article précédent, un shader est un petit programme qui peut être utilisé pour créer des effets intéressants dans nos projets. Il contient des calculs mathématiques et des listes d'instructions (commandes). Ils nous permettent de traiter la couleur de chaque pixel de la zone couvrant l'objet sur notre écran d'ordinateur ou de travailler avec des transformations d'objets (par exemple, pour créer de l'herbe ou de l'eau dynamique). Ce programme nous permet de dessiner des éléments (en utilisant des systèmes de coordonnées) basés sur les propriétés de notre objet polygonal. Les shaders sont exécutés sur le car il possède une architecture parallèle composée de milliers de petits cœurs efficaces conçus pour gérer des tâches simultanément. Soit dit en passant, le processeur a été conçu pour le traitement série séquentiel. GPU Notez qu'il existe trois types de fichiers liés aux shaders dans Unity. Tout d'abord, nous avons des programmes avec l'extension ".shader" capables de compiler différents types de pipelines de rendu. Deuxièmement, nous avons des programmes avec l'extension ".shadergraph" qui ne peuvent être compilés qu'en URP ou en HDRP. De plus, nous avons des fichiers avec l'extension ".hlsl" qui nous permettent de créer des fonctions personnalisées. Ceux-ci sont généralement utilisés dans un type de nœud appelé fonction personnalisée, qui se trouve dans le Shader Graph. Il existe également un autre type de shader avec l'extension ".cginc" - Compute Shader. Il est associé au CGPROGRAM ".shader", tandis que ".hlsl" est associé au HLSLPROGRAM ".shadergraph". Dans Unity, au moins quatre types de structures sont définis pour la génération de shader. Parmi eux, nous pouvons trouver une combinaison de shaders de vertex et de fragments, de shaders de surface pour le calcul automatique de l'éclairage et de shaders de calcul pour des concepts plus avancés. Une petite excursion dans le langage des shaders Avant de commencer à écrire des shaders en général, nous devons tenir compte du fait qu'il existe trois langages de programmation de shaders dans Unity : HLSL (langage de shader de haut niveau - Microsoft) Cg (C pour Graphics - NVIDIA) - un format obsolète ShaderLab - un langage déclaratif - Unity Nous allons parcourir rapidement Cg, et aborder un peu HLSL. ShaderLab Cg est un langage de programmation de haut niveau conçu pour être compilé sur la plupart des GPU. NVIDIA l'a développé en collaboration avec Microsoft et a utilisé une syntaxe très similaire à HLSL. La raison pour laquelle les shaders fonctionnent avec le langage Cg est qu'ils peuvent compiler avec HLSL et GLSL (OpenGL Shading Language), accélérant et optimisant le processus de création de matériel pour les jeux vidéo. Tous les shaders dans Unity (à l'exception de Shader Graph et Compute) sont écrits dans un langage déclaratif appelé ShaderLab. La syntaxe de ce langage nous permet d'afficher les propriétés du shader dans l'inspecteur Unity. Ceci est très intéressant car nous pouvons manipuler les valeurs des variables et des vecteurs en temps réel, en personnalisant notre shader pour obtenir le résultat souhaité. Dans ShaderLab, nous pouvons définir manuellement plusieurs propriétés et commandes, parmi lesquelles le Fallback. Il est compatible avec les différents types de pipelines de rendu qui existent dans Unity. Fallback est un bloc de code fondamental dans les jeux multiplateformes. Cela nous permet de compiler un autre shader à la place de celui qui a généré l'erreur. Si un shader tombe en panne pendant la compilation, Fallback renvoie un autre shader et le matériel graphique peut continuer son travail. Ceci est nécessaire pour ne pas avoir à écrire des shaders différents pour Xbox et PlayStation mais utiliser des shaders unifiés. Types de shader de base dans Unity Les types de shader de base dans Unity nous permettent de créer des sous-programmes à utiliser à des fins différentes. Discutons de ce dont chaque type est responsable : Ce type de shader se caractérise par l'optimisation de l'écriture de code qui interagit avec le modèle d'éclairage de base et ne fonctionne qu'avec Built-In RP. Ombrage de surface standard. Shader éteint. Il fait référence au modèle de couleur primaire et sera la structure de base que nous utilisons généralement pour créer nos effets. Structurellement, il est très similaire au shader Unlit. Ces shaders sont principalement utilisés dans les effets de post-traitement Built-In RP et nécessitent la fonction "OnRenderImage()" (C#). Shader d'effet d'image. Ce type se caractérise par le fait qu'il est exécuté sur la carte vidéo et est structurellement très différent des shaders mentionnés précédemment. Compute Shader. Un shader de type expérimental qui permet de collecter et de traiter le ray tracing en temps réel. Il fonctionne uniquement avec HDRP et DXR. Shader Ray Tracing. Un shader vide basé sur un graphique avec lequel vous pouvez travailler sans connaître les langages de shader utilisant des nœuds. Graphique de shader vierge. C'est un sous-shader qui peut être utilisé dans d'autres shaders Shader Graph. Sous-graphique. Le rendu dans Unity est un sujet difficile, alors lisez attentivement ce guide. Structure du shader Pour analyser la structure des shaders, il suffit de créer un shader simple basé sur Unlit et de l'analyser. Lorsque nous créons un shader pour la première fois, Unity ajoute du code par défaut pour faciliter le processus de compilation. Dans le shader, on peut trouver pour que le GPU puisse les interpréter. des blocs de code structurés Si nous ouvrons notre shader, sa structure ressemble à : Shader "Unlit/OurSampleShaderUnlit" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags {"RenderType"="Opaque"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler 2D _MainTex; float4 _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o, o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } } Avec l'exemple actuel et sa structure de base, cela devient un peu plus clair. Un shader commence par un chemin dans l'inspecteur de l'éditeur Unity (InspectorPath) et un nom (shaderName), puis des propriétés (par exemple, des textures, des vecteurs, des couleurs, etc.), puis SubShader. Au final, un paramètre optionnel Fallback prend en charge différentes variantes. Travailler avec ShaderLab La plupart des shaders commencent par déclarer le shader et son chemin dans l'inspecteur Unity, ainsi que son nom. Les deux propriétés, telles que SubShader et Fallback, sont écrites dans le champ "Shader" dans le langage déclaratif ShaderLab. Shader "OurPath/shaderName" { // The shader code will be here } Le chemin et le nom du shader peuvent être modifiés selon les besoins dans le projet. Les propriétés du shader correspondent à une liste de paramètres pouvant être manipulés depuis l'inspecteur Unity. Il existe huit propriétés différentes, à la fois en termes de valeur et d'utilité. Nous utilisons ces propriétés par rapport au shader que nous voulons créer ou modifier, soit dynamiquement, soit à l'exécution. La syntaxe pour déclarer une propriété est la suivante : PropertyName ("display name", type) = defaultValue "PropertyName" représente le nom de la propriété (par exemple, _MainTex), "display name" spécifie le nom de la propriété dans l'inspecteur Unity (par exemple, Texture), "type" indique son type (par exemple, Color, Vector, 2D, etc.). Enfin, "defaultValue" est la valeur par défaut attribuée à la propriété (par exemple, si la propriété est "Color", nous pouvons la définir sur blanc (1, 1, 1, 1, 1, 1). Le deuxième composant d'un shader est le SubShader. Chaque shader se compose d'au moins un SubShader pour un chargement parfait. Lorsqu'il y a plus d'un SubShader, Unity traitera chacun d'eux et sélectionnera le plus approprié en fonction des spécifications matérielles, en commençant par le premier et en terminant par le dernier de la liste (par exemple, pour séparer le shader pour iOS et Android). Lorsque SubShader n'est pas pris en charge, Unity essaiera d'utiliser le composant Fallback correspondant au shader standard afin que le matériel puisse continuer sa tâche sans erreurs graphiques. Shader "OurPack/OurShader" { Properties { ... } SubShader { // Here will be the shader configuration } } Vous pouvez en savoir plus sur les paramètres et les sous-shaders et . ici ici Mélange Nous avons besoin d'un mélange pour le processus de combinaison de deux pixels en un seul. Le mélange est pris en charge à la fois dans Built-In et SRP. Le mélange se produit dans l'étape qui combine la couleur finale d'un pixel avec sa profondeur. Cette étape se produit à la fin du pipeline de rendu après l'étape de nuanceur de fragment lors de l'exécution du tampon stencil, du tampon z et du mélange de couleurs. Par défaut, cette propriété n'est pas écrite dans le shader, car il s'agit d'une fonctionnalité facultative et est principalement utilisée lorsque vous travaillez avec des objets transparents. Par exemple, il est utilisé lorsque nous devons dessiner un pixel avec un pixel à faible opacité devant un autre (ceci est souvent utilisé dans l'interface utilisateur). Nous pouvons activer le mélange ici : Blend [SourceFactor] [DestinationFactor] Vous pouvez en savoir plus sur le mélange . ici Tampon Z (tampon de profondeur) Pour comprendre les deux concepts, nous devons d'abord apprendre comment fonctionnent le Z-Buffer (également connu sous le nom de Depth Buffer) et le test de profondeur. Avant de commencer, nous devons considérer que les pixels ont des valeurs de profondeur. Ces valeurs sont stockées dans le Depth-Buffer, qui détermine si un objet passe devant ou derrière un autre objet sur l'écran. Le test de profondeur, en revanche, est une condition qui détermine si un pixel sera mis à jour ou non dans le Depth-Buffer. Comme nous le savons déjà, un pixel a une valeur assignée qui est mesurée en couleur RVB et stockée dans le tampon de couleur. Z-buffer ajoute une valeur supplémentaire qui mesure la profondeur du pixel en termes de distance de la caméra, mais uniquement pour les surfaces qui se trouvent dans sa zone frontale. Cela permet à 2 pixels d'avoir la même couleur mais une profondeur différente. Plus l'objet est proche de la caméra, plus la valeur du tampon Z est petite et les pixels avec des valeurs de tampon plus petites écrasent les pixels avec des valeurs plus grandes. Pour comprendre le concept, supposons que nous ayons une caméra et quelques primitives dans notre scène, et qu'elles soient toutes situées sur l'axe spatial "Z". Le mot "tampon" fait référence à "l'espace mémoire" où les données seront temporairement stockées, de sorte que le tampon Z fait référence aux valeurs de profondeur entre les objets de notre scène et la caméra qui sont attribuées à chaque pixel. Nous pouvons contrôler le test de profondeur grâce aux paramètres ZTest dans Unity. Abattage Cette propriété, compatible avec Built-In RP et URP/HDRP, contrôle les faces du polygone qui seront supprimées lors du traitement de la profondeur de pixel. Qu'est-ce que cela signifie? Rappelez-vous qu'un objet polygone a des bords intérieurs et des bords extérieurs. Par défaut, les bords extérieurs sont visibles (CullBack). Cependant, nous pouvons activer les bords intérieurs : Les deux bords de l'objet sont rendus Abattage. Par défaut, les bords arrière de l'objet sont affichés Reculez. Les bords avant de l'objet sont rendus. Avant d'abattage. Cette commande a trois valeurs, à savoir Back, Front et Off. La commande Retour est active par défaut ; cependant, généralement, la ligne de code associée à l'élimination n'est pas visible dans le shader à des fins d'optimisation. Si nous voulons changer les paramètres, nous devons ajouter le mot "Cull" suivi du mode que nous voulons utiliser. Shader "Culling/OurShader" { Properties { [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0 } SubShader { // Cull Front // Cull Off Cull [_Cull] } } Nous pouvons également configurer dynamiquement via la dépendance "UnityEngine.Rendering.CullMode". C'est Enum et est passé comme argument à une fonction. les paramètres Culling dans l'inspecteur Unity Utilisation de Cg/HLSL Dans notre shader, nous pouvons trouver au moins trois variantes de directives par défaut. Ce sont des directives de processeur incluses dans Cg ou HLSL. Leur fonction est d'aider notre shader à reconnaître et à compiler certaines fonctions qui autrement ne peuvent pas être reconnues comme telles : Il permet à l'étape de vertex shader d'être compilée dans le GPU en tant que vertex shader. #pragma sommet vert. Cette directive remplit la même fonction que pragma vertex, à la différence qu'elle permet à une étape de fragment shader appelée "frag" d'être compilée en tant que fragment shader dans le code #pragma fragment fragment. Contrairement aux directives précédentes, elle a une double fonction. Tout d'abord, multi_compile fait référence à un shader de variantes qui nous permet de générer des variantes avec des fonctionnalités différentes dans notre shader. Deuxièmement, le mot "_fog" inclut la fonctionnalité de brouillard de la fenêtre d'éclairage dans Unity. Si nous allons dans Environnement/Autre paramètre, nous pouvons activer ou désactiver les options de brouillard de notre shader. #pragma multi_compile_fog. Nous pouvons également brancher des fichiers Cg/HLSL dans notre shader. Généralement, nous le faisons lorsque nous connectons UnityCG.cginc. Il inclut les coordonnées du brouillard, les positions des objets pour le découpage, les transformations de texture, le report du brouillard et bien plus encore, y compris les constantes UNITY_PI. La chose la plus importante que nous puissions faire avec Cg/HLSL est d'écrire des fonctions de traitement direct pour les shaders de vertex et de fragments, d'utiliser des variables de ces langages et diverses coordonnées comme les coordonnées de texture (TEXCOORD0). #pragma vertex vert #pragma fragment frag v2f vert (appdata v) { // Ability to work with the vertex shader } fixed4 frag (v2f i) : SV_Target { // Ability to work with fragment shader } Vous pouvez en savoir plus sur Cg/HLSL . ici Graphique d'ombrage Shader Graph est une nouvelle solution pour Unity qui vous permet de créer vos solutions sans connaissance du langage shader. Des nœuds visuels sont utilisés pour travailler avec (mais personne n'interdit de les combiner avec le langage de shader). Shader Graph ne fonctionne qu'avec HDRP et URP. Vous devez vous rappeler que lorsque vous travaillez avec Shader Graph, les versions développées pour Unity 2018 sont des versions BETA et ne sont pas prises en charge. Les versions développées pour Unity 2019.1+ sont activement compatibles et bénéficient d'une assistance. Un autre problème est qu'il est très probable que les shaders créés avec cette interface ne se compilent pas correctement dans différentes versions. En effet, de nouvelles fonctionnalités sont ajoutées à chaque mise à jour. Alors, Shader Graph est-il un bon outil pour le développement de shaders ? Bien sûr, c'est le cas. Et il peut être géré non seulement par un programmeur graphique, mais également par un concepteur technique ou un artiste. Pour créer un graphique, il suffit de sélectionner le type souhaité dans l'éditeur Unity. Avant de commencer, introduisons brièvement le vertex/fragment shader au niveau Shader Graph. Comme nous pouvons le voir, il y a trois points d'entrée définis dans l'étape du vertex shader, à savoir : Position(3), Normal(3) et Tangent(3), tout comme dans un shader Cg ou HLSL. Comparé à un shader normal, cela signifie que Position(3) = POSITION[n], Normal(3) = NORMAL[n] et Tangent(3) = TANGENT[n]. Pourquoi un Shader Graph a-t-il trois dimensions, mais un Cg ou HLSL en a-t-il 4 ? Rappelons que les quatre dimensions d'un vecteur correspondent à sa composante W, qui est "un ou zéro" dans la plupart des cas. Lorsque W = 1, le vecteur correspond à une position spatiale ou ponctuelle. Alors que lorsque W = 0, le vecteur correspond à une direction de l'espace. Donc, pour configurer notre shader, nous allons d'abord dans l'éditeur et créons deux paramètres : color - _Color et Texture2D - _MainTex. Pour créer un lien entre les propriétés de ShaderLab et notre programme, nous devons créer des variables dans le champ CGPROGRAM. Cependant, ce processus est différent dans Shader Graph. Nous devons faire glisser et déposer les propriétés que nous voulons utiliser dans l'espace de travail du nœud. Tout ce que nous devons faire pour que Texture2D fonctionne en conjonction avec le nœud Sample Texture 2D est de connecter la sortie de la propriété _MainTex à l'entrée Texture(T2). Pour multiplier les deux nœuds (couleur et texture), il suffit d'appeler le nœud Multiplier et de transmettre les deux valeurs comme point d'entrée. Enfin, nous devons envoyer la sortie couleur dans la couleur de base au stade du fragment shader. Maintenant, enregistrez le shader et nous avons terminé. Notre premier shader est prêt. Nous pouvons également nous tourner vers la configuration générale du graphique, divisée en sections Node et Graph. Ils ont des propriétés personnalisables qui nous permettent de changer la reproduction des couleurs. Nous pouvons trouver des options pour le mélange, l'écrêtage alpha, etc. De plus, nous pouvons personnaliser les propriétés des nœuds dans notre configuration Shader Graph. Les nœuds eux-mêmes fournissent des analogues de certaines fonctions que nous écrivons dans ShaderLab. A titre d'exemple, le code de la fonction Clamp : void Unity_Clamp_float4(float4 In, float4 Min, float4 Max, out float4 Out) { Out = clamp(In, Min, Max); } De cette façon, nous pouvons nous simplifier la vie et réduire le temps d'écriture des shaders au détriment des graphiques visuels. Conclusion Je peux parler beaucoup et longtemps des shaders, ainsi que du processus de rendu lui-même. J'ai abordé toutes les bases de ce . Ici, je n'ai pas discuté des shaders de lancer de rayons et de Compute-Shading. J'ai couvert superficiellement les langages de shader et décrit les processus uniquement à partir de la pointe de l'iceberg. guide sur le rendu dans Unity