您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页Unity局部高效实时阴影

Unity局部高效实时阴影

来源:二三娱乐

无意间看到一篇文章,说是Unity5 demo中为了实现角色的良好阴影,单独给角色设计了一个角色阴影系统。而且使用的是比较老的技术,但效果很好。其实在很多时候,我们需要的并不是万能的阴影光照系统,而是局部能做到效果就行。

#pragma strict
// Offsets camera's rendering from the transform's position.
public var offset: Vector3 = new Vector3(0, 1, 0);
var camera: Camera;
function Start() {
camera = GetComponent.();
}
function LateUpdate() {
var camoffset: Vector3 = new Vector3(-offset.x, -offset.y, offset.z);
var m: Matrix4x4 = Matrix4x4.TRS(camoffset, Quaternion.identity, new Vector3(1, 1, -1));
camera.worldToCameraMatrix = m * transform.worldToLocalMatrix;
}
不过实际使用过程中,我们也许并不需要正确的矩阵赋值,因为你需要的是保证所有的物体在摄像机范围内,只需要知道AABB盒,然后把相机设置在AABB盒的中心,同时增加Size即可。
public ListCharactorList;
void CreateCameraProjecterMatrix()
{
Vector3 v3MaxPosition = -Vector3.one * 500000.0f;
Vector3 v3MinPosition = Vector3.one * 500000.0f;
for (int vertId = 0; vertId < CharactorList.Count; ++vertId)
{
// Light view space
Vector3 v3Position = camera1.worldToCameraMatrix.MultiplyPoint3x4(CharactorList[vertId].position);
if (v3Position.x > v3MaxPosition.x)
{
v3MaxPosition.x = v3Position.x;
}
if (v3Position.y > v3MaxPosition.y)
{
v3MaxPosition.y = v3Position.y;
}
if (v3Position.z > v3MaxPosition.z)
{
v3MaxPosition.z = v3Position.z;
}
if (v3Position.x < v3MinPosition.x)
{
v3MinPosition.x = v3Position.x;
}
if (v3Position.y < v3MinPosition.y)
{
v3MinPosition.y = v3Position.y;
}
if (v3Position.z < v3MinPosition.z)
{
v3MinPosition.z = v3Position.z;
}
}
Vector3 off = v3MaxPosition - v3MinPosition;
Vector3 sizeOff = off;
sizeOff.z = 0;
float dis = sizeOff.magnitude;
//CreateOrthogonalProjectMatrix (ref m_projMatrix, v3MaxPosition, v3MinPosition);
//Debug.Log (v3MaxPosition.ToString() + v3MinPosition.ToString());
//Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1, 1, -1));
//camera1.projectionMatrix = m * m_projMatrix;
camera1.orthographicSize = dis / 1.8f;
camera1.farClipPlane = off.z + 50;
}
void CreateViewMatrix(ref Matrix4x4 viewMatrix,Vector3 look,Vector3 up,Vector3 right,Vector3 pos)
{
look.Normalize ();
up.Normalize ();
right.Normalize ();
float x = -Vector3.Dot (right,pos);
float y = -Vector3.Dot (up,pos);
float z = -Vector3.Dot (look,pos);
viewMatrix.m00 = right.x; viewMatrix.m10 = up.x; viewMatrix.m20 = look.x; viewMatrix.m30 = 0.0f;
viewMatrix.m01 = right.y; viewMatrix.m11 = up.y; viewMatrix.m21 = look.y; viewMatrix.m31 = 0.0f;
viewMatrix.m02 = right.z; viewMatrix.m12 = up.z; viewMatrix.m22 = look.z; viewMatrix.m32 = 0.0f;
viewMatrix.m03 = x; viewMatrix.m13 = y; viewMatrix.m23 = z; viewMatrix.m33 = 1.0f;
}
void CreateOrthogonalProjectMatrix(ref Matrix4x4 projectMatrix,Vector3 v3MaxInViewSpace, Vector3 v3MinInViewSpace)
{
float scaleX, scaleY, scaleZ;
float offsetX, offsetY, offsetZ;
scaleX = 2.0f / (v3MaxInViewSpace.x - v3MinInViewSpace.x);
scaleY = 2.0f / (v3MaxInViewSpace.y - v3MinInViewSpace.y);
offsetX = -0.5f * (v3MaxInViewSpace.x + v3MinInViewSpace.x) * scaleX;
offsetY = -0.5f * (v3MaxInViewSpace.y + v3MinInViewSpace.y) * scaleY;
scaleZ = 1.0f / (v3MaxInViewSpace.z - v3MinInViewSpace.z);
offsetZ = -v3MinInViewSpace.z * scaleZ;
//列矩阵
projectMatrix.m00 = scaleX; projectMatrix.m01 = 0.0f; projectMatrix.m02 = 0.0f; projectMatrix.m03 = offsetX;
projectMatrix.m10 = 0.0f; projectMatrix.m11 = scaleY; projectMatrix.m12 = 0.0f; projectMatrix.m13 = offsetY;
projectMatrix.m20 = 0.0f; projectMatrix.m21 = 0.0f; projectMatrix.m22 = scaleZ; projectMatrix.m23 = offsetZ;
projectMatrix.m30 = 0.0f; projectMatrix.m31 = 0.0f; projectMatrix.m32 = 0.0f; projectMatrix.m33 = 1.0f;
}

你看 所有角色都被包括在内了。当然具体适合的值你可以自己调整,这样我们就解决了第一个问题。 如果你的应用场景在室内,你可以无视第一个问题,直接手动设置一个
最合适的值就行了。
第二部,就是我们需要获得物体的剪影。就是说将物体的外轮廓给检录下来。当然复杂点就是获得物体的深度图。剪影获得很简单,我们看下深度图如何获得。因为在移动平台上不支持自动生成深度图,所以我打算自己使用片段着色器获得。

Shader "depthShader" {
Properties {
}
SubShader {
//Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11 and Xbox360; has structs without semantics (struct v2f members pos1)
#pragma exclude_renderers d3d11 xbox360
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D_float _CameraDepthTexture;
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
half4 pos : SV_POSITION;
float2 depth;
};
v2f vert (appdata_base v) {
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.depth = o.pos.zw;
return o;
}
fixed4 frag(v2f i) : COLOR
{
float d = i.depth.x/i.depth.y;
float flag = 0;
if(d < 0)
{
d = abs(d);
flag = 1;
}
float3 kEncodeMul = float3(1.0, 255.0, 65025.0);
float kEncodeBit = 1.0/255.0;
float3 enc = kEncodeMul * d;
enc = frac (enc);
enc -= enc.yzz * kEncodeBit;
return fixed4(flag, enc);
}
ENDCG
}
}
}

本来只要存储z就好了,不过很遗憾的是,有些平台的z竟然是负值,负值存储成像素是无意义的。所以我用r存储正负值,gba保存数值,但这样子性能确实会下降,毕竟在片段着色器里,考虑到后面需要照顾阴影质量,我觉得不使用深度图,而使用剪影。
剪影就简单了,连着色器都不用自己写,直接用摄像机渲染的黑图即可。这里我将摄像机设置成普通渲染模式,手动调用render,将贴图放到rendertexture中。

camera1 = GameObject.Find ("Camera").camera;
//camera1.hideFlags = HideFlags.HideAndDontSave;
camera1.enabled = false;
//camera1.projectionMatrix = camera.projectionMatrix;
int textureSize = 1024;
shadowTexture = new RenderTexture(textureSize , textureSize, 16, RenderTextureFormat.ARGB32);
shadowTexture.name = "shadowTexture" + GetInstanceID();
shadowTexture.isPowerOfTwo = true;
shadowTexture.hideFlags = HideFlags.DontSave;
camera1.targetTexture = shadowTexture;

这样就可以看到如下贴图:


1024大小,内存6M 还算可以接受。当然,因为深度没有用,可以取消,这样会变成4M,其他格式可能会更小,但手机上不一定支持,所以暂时先这样吧。
第三个问题,就是怎么把这些东西投射到地上变成影子。
首先投射到地上已经有现成的Projector组件了,所以问题变成了坐标计算。
我们先把Projector的位置确定一下,Projector应该和主摄像机放在同一个地方,同时有同样的参数设置:

proj = GameObject.Find ("GameObject").GetComponent();
proj.nearClipPlane = camera.nearClipPlane;
proj.farClipPlane = camera.farClipPlane;
proj.fieldOfView = camera.fieldOfView;

这样就保证了视角内的物体都会出现阴影。然后就是坐标计算了,我们想一下,假设世界坐标中的点a,那么我们计算出它在正交摄像机中的坐标,然后根据坐标取出投影贴图中的点,那么假如这个点是全黑的(看你设置的是啥值了),那么就是不在阴影区的,假如不是,那么说明是阴影。
有了这个方案,我们就开始计算吧。首先我们可以轻易获得物体的坐标,问题在于怎么获得它在正交摄像机中的坐标,这个就需要使用正交摄像机的矩阵获得:

matVP = GL.GetGPUProjectionMatrix (camera1.projectionMatrix, true) * camera1.worldToCameraMatrix;
proj.material.SetMatrix("ShadowMatrix", matVP);

注意,我将这个计算出来的矩阵赋值给了一个材质,这个材质就是Projector使用的,因为是它需要根据坐标判断是否有阴影。
这样似乎接下来就可以直接写出着色器了:

v2f vert (appdata_base v)
{
v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
float4x4 matWVP = mul (ShadowMatrix, _Object2World);
o.uvShadow = mul(matWVP, v.vertex);
return o;
}

注意,顶点着色器不仅要计算出pos,同时还要获得正交相机中的坐标,也就是uvShadow.
然后就可以在片段着色器中处理了:

half2 uv = i.uvShadow.xy / i.uvShadow.w * 0.5 + 0.5;
#if UNITY_UV_STARTS_AT_TOP
uv.y = 1 - uv.y;
#endif
fixed4 res = fixed4(0, 0, 0, 0);
fixed4 texS = tex2D(_ShadowTex, uv);
if(texS.a > 0)
{
res.a = 0.5;
}

就这么几行,阴影就出现了:


但效果似乎不那么尽如人意,让我们看下u3d自带的高品质的阴影效果:


其实已经很接近了呢,不过鉴于我开头宣称要高质量阴影,所以我打算继续优化边缘,因为你可以看到边缘部分的锯齿,当然我们可以单纯增加贴图大小,但是假如到2048,那么就要占据16M的内存了,所以我暂时打算用另外一种做法,pcf。就是通过采样,将边缘像素模糊化。

texS = tex2D(_ShadowTex, uv + half2(-0.94201624/pad, -0.39906216/pad));
if(texS.a > 0)
{
res.a += _Strength;
}
texS = tex2D(_ShadowTex, uv + half2(0.94558609/pad, -0.76890725/pad));
if(texS.a > 0)
{
res.a += _Strength;
}
texS = tex2D(_ShadowTex, uv + half2(-0.094184101/pad, -0.92938870/pad));
if(texS.a > 0)
{
res.a += _Strength;
}
texS = tex2D(_ShadowTex, uv + half2(0.34495938/pad, 0.29387760/pad));
if(texS.a > 0)
{
res.a += _Strength;
}

经过采样后效果如下:


最后 是时候做一次全面比较了,在电脑上我自己写的完爆自带的,因为我的电脑的显卡很渣,但手机显卡好一些,所以手机上的结果可能不太一样,放到手机上,开启最强模式,看看到底性能和效果对比吧。找了一台两年前的1000块钱的华为:
u3d 高质量阴影:
近距离效果:


再看我自己实现的效果:
2048最强模式下:
近距离:


不过帧数下降了不少,再看看1024的吧。


效果也是要好上不少吧。
后来做了一些优化,主要是动态调整正交摄像机的位置, 这样就能够在远距离时候使用模糊阴影,近距离使用高清阴影了, 在我们自己的项目中使用, 效果十分可以。
优化代码:

public class VisibleMesh:MonoBehaviour
{
public Transform tr;
void OnWillRenderObject()
{
if(Camera.current == Camera.main)
{
if(SceneShadow.inst.CharactorList.Contains(tr))
{
return;
}
SceneShadow.inst.CharactorList.Add (tr);
}
}
}
Vector3 off = v3MaxPosition - v3MinPosition;
Vector3 pos = (v3MaxPosition + v3MinPosition) / 2;
pos = camera1.cameraToWorldMatrix.MultiplyPoint3x4 (pos);
camera1.transform.position = pos - camera1.transform.forward * (off.z + 10);
Vector3 sizeOff = off;
sizeOff.z = 0;
float dis = sizeOff.x;
if(sizeOff.y > dis)
{
dis = sizeOff.y;
}
camera1.orthographicSize = dis / 2;
camera1.farClipPlane = off.z + 30;

Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务