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"
}
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();
}
}