Billboards transparent avec ombres tramées selon l'alpha

Questions à propos du scripting Shader.
Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Billboards transparent avec ombres tramées selon l'alpha

Message par Alesk » 08 Mars 2016 00:20

Yo,

Voici un shader qui génère des billboards à partir des vertices d'un mesh. Je suis parti d'un shader existant qui faisait des billboards de brin d'herbe.
J'ai viré tout ce qui ne me servait pas, et j'ai ajouté deux fonctions de tramage pour ne pas avoir des ombres complètement opaques.
Bien entendu, ce n'est pas aussi bien que des dégradés harmonieux, mais comme on a visiblement pas moyen d'obtenir ça dans les shadow maps, c'est toujours mieux que rien.

Par contre, pour le moment le tramage se fait à partir d'une fonction random à partir de la position écran de chaque fragment, ce qui n'est pas tip top car ça induit aussi un mouvement aléatoire du bruit dès qu'il y a un déplacement de la caméra, de la lumière ou de l'objet.
Donc c'est encore en chantier, et si quelqu'un à une idée pour générer le même type de tramage, mais qui reste fixe (indépendant des mouvements cités plus haut), je suis preneur ^_^

Code : Tout sélectionner

Shader "Custom/BillboardsWithShadows"
{
	Properties
	{
		_MainTex ("Grass Texture", 2D) = "white" {}
		_MaxCameraDistance ("Max Camera Distance", float) = 250
		_Size ("Size", Range(0.01,2)) = 1
		_Transition ("Transition", float) = 30
		_RandomDithering("Random Dithering", Range(0,1)) = 0.5
		_CheckerDithering("Checker Dithering", Range(0,0.5)) = 0
	}

	SubShader
	{
		Tags { "Queue" = "Transparent" "RenderType"="Transparent" }

		// base pass
		Pass
		{
			Tags { "LightMode" = "ForwardBase"}
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off
	   		Lighting Off
	   		ZWrite Off

			CGPROGRAM
				#pragma vertex vertexShader
				#pragma fragment fragmentShader
				#pragma geometry geometryShader

				#pragma multi_compile_fwdbase
				#pragma multi_compile_fog

				#pragma fragmentoption ARB_precision_hint_fastest

				#include "UnityCG.cginc"
				#include "AutoLight.cginc"

				struct VS_INPUT
				{
					float4 position : POSITION;
					float4 uv_Noise : TEXCOORD0;
					fixed sizeFactor : TEXCOORD1;
					float4 color: COLOR;

				};

				struct GS_INPUT
				{
					float4 worldPosition : TEXCOORD0;
					float4 color: COLOR;

				};

				struct FS_INPUT
				{
					float4 pos	: SV_POSITION;		// has to be called this way because of unity MACRO for light
					float2 uv_MainTexture : TEXCOORD0;
					float4 tint : COLOR0;

					LIGHTING_COORDS(1,2)
					UNITY_FOG_COORDS(3)
				};

				uniform sampler2D _MainTex, _NoiseTexture;

				// for billboard
				uniform float _Size;
				uniform float _MaxCameraDistance;
				uniform float _Transition;

				uniform float4 _LightColor0;


				// Vertex Shader ------------------------------------------------
				GS_INPUT vertexShader(VS_INPUT vIn)
				{
					GS_INPUT vOut;

					// set output values
					vOut.worldPosition =  mul(_Object2World, vIn.position);
					vOut.color = vIn.color;

					return vOut;
				}


				// Geometry Shader -----------------------------------------------------
				[maxvertexcount(4)]
				void geometryShader(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
				{
					// cutout trough a transition area
					float cameraDistance = length(_WorldSpaceCameraPos - p[0].worldPosition);

					// discard billboards that are too far away
					if (cameraDistance > _MaxCameraDistance)
						return;

					float t = (cameraDistance - (_MaxCameraDistance - _Transition)) / _Transition;
					float alpha = clamp (1, 0, lerp (1.0, 0.0, t));

					float3 viewDirection = UNITY_MATRIX_V[2].xyz;
					float3 up = UNITY_MATRIX_V[1].xyz;
					float3 right = cross(up, viewDirection);

					// billboard's size and color
					float halfSize = 0.5f * _Size;

					float4 tint = p[0].color;
					tint.a = alpha * p[0].color.a;

					// create billboard
					float4 v[4];
					v[0] = float4(p[0].worldPosition + halfSize * right - halfSize * up, 1.0f);
					v[1] = float4(p[0].worldPosition + halfSize * right + halfSize * up, 1.0f);
					v[2] = float4(p[0].worldPosition - halfSize * right - halfSize * up, 1.0f);
					v[3] = float4(p[0].worldPosition - halfSize * right + halfSize * up, 1.0f);

					// matrix to transfer vertices from world to screen space
					float4x4 vpMatrix = mul(UNITY_MATRIX_MVP, _World2Object);

					FS_INPUT fIn;

					fIn.pos = mul(vpMatrix, v[0]);
					fIn.uv_MainTexture = float2(1.0f, 0.0f);
					fIn.tint = tint;
					TRANSFER_VERTEX_TO_FRAGMENT(fIn);
					UNITY_TRANSFER_FOG(fIn,fIn.pos);

					triStream.Append(fIn);

					fIn.pos =  mul(vpMatrix, v[1]);
					fIn.uv_MainTexture = float2(1.0f, 1.0f);
					fIn.tint = tint;
					TRANSFER_VERTEX_TO_FRAGMENT(fIn);
					UNITY_TRANSFER_FOG(fIn,fIn.pos);

					triStream.Append(fIn);

					fIn.pos =  mul(vpMatrix, v[2]);
					fIn.uv_MainTexture = float2(0.0f, 0.0f);
					fIn.tint = tint;
					TRANSFER_VERTEX_TO_FRAGMENT(fIn);
					UNITY_TRANSFER_FOG(fIn,fIn.pos);

					triStream.Append(fIn);

					fIn.pos =  mul(vpMatrix, v[3]);
					fIn.uv_MainTexture = float2(0.0f, 1.0f);
					fIn.tint = tint;
					TRANSFER_VERTEX_TO_FRAGMENT(fIn);
					UNITY_TRANSFER_FOG(fIn,fIn.pos);

					triStream.Append(fIn);
				}


				// Fragment Shader -----------------------------------------------
				float4 fragmentShader(FS_INPUT fIn) : COLOR
				{
					fixed4 color = tex2D(_MainTex, fIn.uv_MainTexture) * fIn.tint;
					if (color.a < 0.01) discard;

					float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
					float atten = LIGHT_ATTENUATION(fIn);

					float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
					float3 normal = float3(0,1,0);
					float3 lambert = float(max(0.0,dot(normal,lightDirection)));
					float3 lighting = (ambient + lambert * atten) * _LightColor0.rgb;

					color = fixed4 (color.rgb * lighting, color.a);

					UNITY_APPLY_FOG(fIn.fogCoord, color);

					return color;
				}

			ENDCG
		}

		// shadow caster
		Pass
		{
			//Name "ShadowCaster"
			Tags { "LightMode" = "ShadowCaster" }

			Fog { Mode Off }
			ZWrite On
			ZTest LEqual

			CGPROGRAM

			#pragma vertex	vertexShader
			#pragma geometry geometryShader
			#pragma fragment fragmentShader

			//#pragma multi_compile_shadowcaster
			//#pragma only_renderers d3d11
			//#define SHADOW_CASTER_PASS

			#include "UnityCG.cginc"
			#include "HLSLSupport.cginc"

			struct VS_INPUT
			{
				float4 position : POSITION;
				float4 uv_Noise : TEXCOORD0;
				fixed sizeFactor : TEXCOORD1;
			};

			struct SHADOW_VERTEX
			{
				float4 vertex : POSITION; // has to be called this way because of unity macro
			};

			struct GS_INPUT
			{
				float4 worldPosition : TEXCOORD0;
			};

			struct FS_INPUT
			{
				float2 uv_MainTexture : TEXCOORD0;
				V2F_SHADOW_CASTER;
			};

			uniform sampler2D _MainTex, _NoiseTexture;

			// for billboard
			uniform float _Size;
			uniform float _Transition;
			uniform float _MaxCameraDistance;
			uniform fixed _RandomDithering;
			uniform fixed _CheckerDithering;


		   GS_INPUT vertexShader (VS_INPUT v)
		   {
				GS_INPUT vOut;
				// set output values
				vOut.worldPosition =  mul(_Object2World, v.position);
				return vOut;
		   }

		   // Geometry Shader
		   [maxvertexcount(4)]
		   void geometryShader(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream )
		   {
				// cutout trough a transition area
				float cameraDistance = length(_WorldSpaceCameraPos - p[0].worldPosition);

				// discard billboards that are too far away
				if (cameraDistance > _MaxCameraDistance)
					return;

				float t = (cameraDistance - (_MaxCameraDistance - _Transition)) / _Transition;
				float alpha = clamp (1, 0, lerp (1.0, 0.0, t));

				float3 viewDirection = UNITY_MATRIX_V[2].xyz;
				float3 up = UNITY_MATRIX_V[1].xyz;
				float3 right = cross(up, viewDirection);

				// size of billboard
				float halfSize = 0.5f * _Size;

				// create billboard
				float4 vertices[4];
				vertices[0] = float4(p[0].worldPosition + halfSize * right - halfSize * up, 1.0f);
				vertices[1] = float4(p[0].worldPosition + halfSize * right + halfSize * up, 1.0f);
				vertices[2] = float4(p[0].worldPosition - halfSize * right - halfSize * up, 1.0f);
				vertices[3] = float4(p[0].worldPosition - halfSize * right + halfSize * up, 1.0f);

				FS_INPUT fIn;

				SHADOW_VERTEX v;
				v.vertex = mul (_World2Object, vertices[0]);
				fIn.uv_MainTexture = float2(1.0f, 0.0f);
				TRANSFER_SHADOW_CASTER(fIn)		// uses "v.vertex" for vertex position

				triStream.Append(fIn);

				v.vertex = mul (_World2Object, vertices[1]);
				fIn.uv_MainTexture = float2(1.0f, 1.0f);
				TRANSFER_SHADOW_CASTER(fIn)

				triStream.Append(fIn);

				v.vertex = mul (_World2Object, vertices[2]);
				fIn.uv_MainTexture = float2(0.0f, 0.0f);
				TRANSFER_SHADOW_CASTER(fIn)

				triStream.Append(fIn);

				v.vertex = mul (_World2Object, vertices[3]);
				fIn.uv_MainTexture = float2(0.0f, 1.0f);
				TRANSFER_SHADOW_CASTER(fIn)

				triStream.Append(fIn);
			}

			float rand(float3 co){
				return frac(sin(dot(co.xyz,float3(12.9898,78.233,45.5432)))*43758.5453);
			}

			fixed4 fragmentShader (FS_INPUT fIn) : COLOR
			{
				fixed4 color = tex2D(_MainTex, fIn.uv_MainTexture);
				if (color.a < 0.01) discard;
				
				// Random dithering depending on alpha
				if (rand(fIn.pos.xyz)*_RandomDithering > color.a) discard;

				// Checker dithering
				if(_CheckerDithering > 0){
					fIn.pos.xy = floor(fIn.pos.xy * (1-_CheckerDithering)) * 0.5;
	            			float checker = -frac(fIn.pos.r + fIn.pos.g);
	            			clip(checker);
	            		}

				SHADOW_CASTER_FRAGMENT(fIn)
			}

			ENDCG
		}
	}

	FallBack "Diffuse"
}

Voici un exemple de ce que ça donne :
billboards_dithered_shadows.jpg
billboards_dithered_shadows.jpg (64.13 Kio) Consulté 5109 fois
à gauche, la version juste avec un cutout, à droite la version avec le tramage (paramètre Random Dithering dans le shader)

J'ai aussi testé avec un autre type de tramage (à base de damier, Checker Dithering) mais c'est pas terrible et cause du moirage que ça provoque.

Là j'ai utilisé un script qui génère un mesh avec uniquement des vertices placés aléatoirement, mais vous pouvez utiliser n'importe quel mesh pour tester.

Vlà mon script pour générer le nuage de points (si vous modifiez le nombre de point, le mesh ne sera regénéré qu'en appuyant sur play)

Code : Tout sélectionner

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
[ExecuteInEditMode]
public class RandomPointCloud : MonoBehaviour {
	private Mesh mesh;
	public int numPoints = 200;
	public float radius = 2;

		void Start () {
			CreateMesh();
		}

		void CreateMesh() {
			mesh = new Mesh();
			GetComponent<MeshFilter>().mesh = mesh;

			Vector3[] points = new Vector3[numPoints];
			int[] indices = new int[numPoints];
			Color[] colors = new Color[numPoints];

			Transform t = this.transform;
			Vector3 campos = t.InverseTransformPoint(Camera.main.transform.position);
			for(int i=0;i<points.Length;++i) {

					points [i] = Vector3.zero;
					points [i].z = i*0.2f;
					points [i] = UnityEngine.Random.insideUnitSphere*radius;
					indices[i] = i;

					colors[i] = new Color(UnityEngine.Random.Range(0.2f,1.0f),UnityEngine.Random.Range (0.2f,1.0f),UnityEngine.Random.Range(0.2f,1.0f),0.5f);
			}

			mesh.vertices = points;
			mesh.colors = colors;
			mesh.SetIndices(indices, MeshTopology.Points,0);
			mesh.MarkDynamic ();
			mesh.RecalculateBounds();
		}
}

Dernière édition par Alesk le 08 Mars 2016 12:15, édité 1 fois.

Avatar de l’utilisateur
F@B
Messages : 1844
Inscription : 01 Août 2013 10:41
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par F@B » 08 Mars 2016 11:52

Salut,

super, tu ne fais pas de pass pour le cutout, c'est pour cela que tu n'as pas de soucis avec le le renderQueue.
La en gros si un poly est devant un autre il passe derrière avec ton ZwriteOff.

Original ton astuce de tramage pour des ombres semi transparente ! :) je me note ça dans un coin.
ʕ·͡ᴥ·ʔ ==> Mon Portfolio <== ʕ·͡ᴥ·ʔ

Merci de lire et de prendre en considération la Nétiquette des Forums avant de poster un sujet !

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par Alesk » 08 Mars 2016 12:13

F@B a écrit :Salut,
super, tu ne fais pas de pass pour le cutout, c'est pour cela que tu n'as pas de soucis avec le renderQueue.
ok, il faudrait que je puisse jeter un oeil sur ton shader dans un cas concret pour que je comprenne mieux où ça coince dans ton cas. Dès que tu peux, passe-moi un petit package ;)
F@B a écrit :La en gros si un poly est devant un autre il passe derrière avec ton ZwriteOff.
Alors en fait, je triche un peu, car dans mon cas précis, je trie les vertices selon leur distance à la caméra pour forcer systématique l'affichage des billboards en back to front, du coup je n'ai pas ce problème d'incohérence qui peut se produire entre la distance et l'ordre de rendu : les billboards sont toujours rendus du plus éloigné au plus proche.
Et pour limiter encore les cas où on peut voir des billboards voisins se passer devant l'un l'autre en fonction de la position de la caméra, l'alpha est limité à 0.5, mais ça ne convient que parce que je cherche à faire de la fumée, donc ce n'est pas gênant.
(mais ce tri n'est pas dans les scripts que j'ai partagé dans mon premier post)
F@B a écrit :Original ton astuce de tramage pour des ombres semi transparente ! :) je me note ça dans un coin.
Merci, il ne me reste plus qu'à trouver comment faire en sorte que ce tramage cesse de donner cette impression de fourmillement ^_^
Sinon j'ai remarqué qu'en activant les cascaded shadows, le tramage devenait un peu plus statique (mais je ne comprends pas pourquoi), et avec des softs shadows en plus, ça passe encore mieux (mais ça reste quand même bien granuleux)

Avatar de l’utilisateur
F@B
Messages : 1844
Inscription : 01 Août 2013 10:41
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par F@B » 08 Mars 2016 14:21

Alors en fait, je triche un peu, car dans mon cas précis, je trie les vertices selon leur distance à la caméra pour forcer systématique l'affichage des billboards en back to front, du
Tu le fais dans le shader ce tri des vertex???? j'ai essayer d'écrire dans le Zbuffer fonction de la camera mais sans succès pour l'instant.
ʕ·͡ᴥ·ʔ ==> Mon Portfolio <== ʕ·͡ᴥ·ʔ

Merci de lire et de prendre en considération la Nétiquette des Forums avant de poster un sujet !

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par Alesk » 08 Mars 2016 16:28

Nope, j'aimerais bien, mais je n'ai pas trouvé comment faire ça dans un shader... Pour le moment c'est fait en C# et ce n'est pas assez rapide à mon goût.

Avatar de l’utilisateur
F@B
Messages : 1844
Inscription : 01 Août 2013 10:41
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par F@B » 08 Mars 2016 16:39

j'ai trouvé un début de piste a étudier, que j'ai pas réussi a mettre en œuvre :
http://forum.unity3d.com/threads/writin ... ram.66153/

ça va, peut être t'aider ?
ʕ·͡ᴥ·ʔ ==> Mon Portfolio <== ʕ·͡ᴥ·ʔ

Merci de lire et de prendre en considération la Nétiquette des Forums avant de poster un sujet !

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par Alesk » 08 Mars 2016 17:06

Nope, ça m'aide pas (même si c'est très intéressant ;) )
Dans mon cas il s'agit de réorganiser "physiquement" l'ordre des vertices dans le tableau de données pour qu'ils soient traités dans le bon ordre par le gpu.
Pour un mesh avec des triangles, il s'agit de réorganiser l'ordre des triangles pour qu'ils soient aussi affichés du plus éloigné au plus proche.
Quand il s'agit de transparence, le depth buffer n'est d'aucune aide, car il ne permet pas de connaitre la profondeur de chacune des couches transparentes qui peuvent se superposer sur un même pixel. :/

Avatar de l’utilisateur
F@B
Messages : 1844
Inscription : 01 Août 2013 10:41
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par F@B » 08 Mars 2016 17:08

Je comprends merci! En effet je me suis fourvoyé... ;-)
ʕ·͡ᴥ·ʔ ==> Mon Portfolio <== ʕ·͡ᴥ·ʔ

Merci de lire et de prendre en considération la Nétiquette des Forums avant de poster un sujet !

Avatar de l’utilisateur
Titan
Messages : 582
Inscription : 12 Sep 2011 13:54
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par Titan » 08 Mars 2016 19:11

qu'est-ce qui t’empêche de passer les coordonnées dans le référentiel global à ton fragment shader pour servir de seed à ton random ?
Avec les arrondis je ne suis pas certains que le fourmillement disparaisse complètement mais ça me semble plus logique que d'utiliser le screenspace.

Sinon, j'ai l'impression que seul l'ombre reçois ton effet et que les billboard ne le reçoivent pas ? ou alors c'est juste une impression ?
____________________________________________
Hop Boy

Avatar de l’utilisateur
Alesk
Messages : 2303
Inscription : 13 Mars 2012 09:09
Localisation : Bordeaux - France
Contact :

Re: Billboards transparent avec ombres tramées selon l'alpha

Message par Alesk » 09 Mars 2016 12:02

Titan a écrit :qu'est-ce qui t’empêche de passer les coordonnées dans le référentiel global à ton fragment shader pour servir de seed à ton random ?
Qu'est-ce que tu entends par "référentiel global" ?
Titan a écrit :Sinon, j'ai l'impression que seul l'ombre reçois ton effet et que les billboard ne le reçoivent pas ? ou alors c'est juste une impression ?
C'est bien le cas, j'ai pour le moment supprimé la réception d'ombres sur les billboards car ça me faisait n'importe quoi :p
Je m'attaquerais à ce problème dans un second temps ^_^

Répondre

Revenir vers « les Shaders »