- Код: Выделить всё
Shader "Shodow" {
Properties {
lightPosition ("Light Pos", Vector)=(0.0,0.0,0.0,0.0)
lightRadius ("Light Radius", Float)=1.0
lightRange ("Light Range", Float)=0.5
zTexture ("Test",2D)="" {}
}
SubShader {
Pass
{
CGPROGRAM
#pragma target 3.0
#pragma vertex ExtrudePenumbra
#pragma fragment PenumbraAlpha
float4x4 invTransform; // invTransform = ~worldViewProjMatrix
float4x4 worldViewMatrix;
float4x4 worldViewProjMatrix;
float4 lightPosition; //координаты источника света
float lightRadius; // радиус источника света
float lightRange;
texture zTexture;
sampler zTextureSampler = sampler_state
{
Texture = (zTexture);
MinFilter = LINEAR;
MagFilter = LINEAR;
};
struct VS_INPUT_VE
{
float4 position : POSITION;
float3 vNormal0 : TEXCOORD0; // нормаль в первой вершине ребра
float3 vNormal1 : TEXCOORD1; // нормаль во второй вершине ребра
float4 normal : NORMAL; // нормаль к первой смежной грани ребра
float3 backNormal : TEXCOORD2; // нормаль ко второй смежной грани ребра
float4 edge : TEXCOORD3; // ребро соединяющее данную вершину ребра с другой
};
struct VS_OUTPUT_VE
{
float4 position : POSITION;
float4 front : TEXCOORD0; // плоскости ограничивающие пенумбральный клин
float4 back : TEXCOORD1;
float4 left : TEXCOORD2;
float4 right : TEXCOORD3;
float4 projPos : TEXCOORD4; // та же позиция, пригодится в пиксельном шейдере
};
// нахождение левой и правой плоскости пенумбрального клина
float4 GetLRPlane(float3 pos, float3 sv0, float3 sv1)
{
float4 plane;
plane.xyz = normalize( cross(pos - sv0, pos - sv1) );
plane.w = -dot(pos, plane.xyz);
return plane;
}
// нахождение передней и задней плоскости пенумбрального клина
float4 GetFBPlane(float3 pos, float3 edge, float3 sv)
{
float4 plane;
plane.xyz = normalize( cross(edge, pos - sv) );
plane.w = -dot(pos, plane.xyz);
return plane;
}
// Вершинный шейдер, выполняющий построение пенумбрального клина
VS_OUTPUT_VE ExtrudePenumbra( VS_INPUT_VE vertex )
{
VS_OUTPUT_VE result;
float4 extruded;
float3 dir;
float3 sphereVert0, sphereVert1;
// Если обе грани ребра освещены, или неосвещены одновременно,
// то данная вершина не входит в пенумбральный клин
dir = vertex.position - lightPosition;
if ( dot(dir, vertex.normal) * dot(dir, vertex.backNormal) > 0.0 )
{
result.front = result.back = result.left = result.right = 0.0;
result.projPos = result.position = 0.0;
return result;
}
// Мы немного увеличим пенумбру, чтобы не было прорези
// между тенью и полутенью
sphereVert0 = lightPosition + vertex.vNormal0 * 0.15;
sphereVert1 = lightPosition - vertex.vNormal0 * lightRadius;
extruded = vertex.position;
if ( vertex.normal.w > 0.0 ) // в w-компоненте указано как вытягивать вершину
{
// вытягивание от источника, вершина лежит в back-плоскости клина
dir = vertex.position.xyz - sphereVert0;
extruded.xyz += normalize(dir) * (lightRange - length(dir));
}
else if ( vertex.normal.w < 0.0 )
{
// вершина лежит в front-плоскости клина
dir = vertex.position.xyz - sphereVert1;
extruded.xyz += normalize(dir) * (lightRange - length(dir));
}
// Еслм w == 0 не вытягиваем вершину
result.projPos = result.position = mul(extruded, worldViewProjMatrix);
// w - хранит ориентацию ребра. Если ребро идёт от первой вершины
// до второй, то w == 1.0, иначе -1.0.
// Необходимо "развернуть" ребро во второй вершине,
// чтобы нормали к front, back плоскостям не поменяли знак
dir = vertex.edge * vertex.edge.w;
// Находим плоскости пенумбрального клина
result.front = GetFBPlane(vertex.position.xyz, dir, sphereVert1);
result.back = -GetFBPlane(vertex.position.xyz, dir, sphereVert0);
// во второй вершине left и right плоскости меняются местами
if (vertex.edge.w > 0.0)
{
result.left = -GetLRPlane(vertex.position.xyz, sphereVert0, sphereVert1);
result.right = GetLRPlane(vertex.position.xyz + vertex.edge.xyz, lightPosition,
sphereVert0 - vertex.vNormal1 * lightRadius);
}
else
{
result.right = -GetLRPlane(vertex.position.xyz, sphereVert0, sphereVert1);
result.left = GetLRPlane(vertex.position.xyz + vertex.edge.xyz, lightPosition,
sphereVert0 - vertex.vNormal1 * lightRadius);
}
return result;
}
// Пиксельный шейдер, рассчитывающий освещённость в точке.
// Работаем в системе координат объекта
float4 PenumbraAlpha( VS_OUTPUT_VE vertex ) : COLOR0
{
// Надо найти текстурную координату, соответствующую точке сцены
float2 projPos = vertex.projPos / vertex.projPos.w;
float2 texel = float2(projPos.x + 1.0, -projPos.y + 1.0) / 2.0;
// Кордината точки в сцене
float4 vertPos = mul(float4(projPos.x, projPos.y, tex2D(zTextureSampler, texel).r, 1.0), invTransform);
// Расстояния до внутренней и внешней плоскости клина
float distInner = dot(vertPos, vertex.back);
float distOuter = dot(vertPos, vertex.front);
// Смотрим, чтобы мы находились внутри пенумбрального клина
if (dot(vertPos, vertex.left) * dot(vertPos, vertex.right) > 0.0 && distInner * distOuter > 0.0)
{
float alpha = distInner / (distOuter + distInner);
// Для сферического источника освещённость в пенумбральном клине
// изменяется не линейно, это небольшое улучшение
return 3*pow(alpha, 2.0) - 2*pow(alpha, 3.0);
}
else
return 1.0;
}
ENDCG
}
}
FallBack "Diffuse"
}
Шейдер не доделан. Если его использовать теней не будет. Выложу когда закончу.