其实这个效果是偶然中发现的,可能也没有什么可以实际运用的地方,只是个人觉得比较好玩,记录一下。
1.先看效果
除了投影以外,也可以做成一个范围外不可见的效果,但和投影一样,都有其他更方便的解决方案,所以也就是图个乐了。
大体思路就是在shader中还原世界空间,再利用sdf函数将空间分为多个部分,最后对不同部分进行不同的处理就可以得到下面的效果了。下图是使用的是球的sdf,球型范围内的区域也就是投影区域了。sdf可以参考3D SDF
2.在shader中还原世界空间
要还原世界空间需要在shader中获得每个像素点的深度信息以及相机到该点的射线方向。
unity中可以通过设置相机的贴图模式来获取深度以及法线信息
camera.depthTextureMode |= DepthTextureMode.DepthNormals;
射线方向可以在脚本中每帧计算相机四个边缘点的方向,然后组合成一个矩阵传给shader并在顶点着色器中解算。这个可以参考乐乐的《UnityShader入门精要》
Matrix4x4 frustumCorners = Matrix4x4.identity;
float fov = m_camera.fieldOfView;
float near = m_camera.nearClipPlane;
float aspect = m_camera.aspect;
float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = transform.right * halfHeight * aspect;
Vector3 toTop = transform.up * halfHeight;
Vector3 topLeft = transform.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near;
topLeft.Normalize();
topLeft *= scale;
Vector3 topRight = transform.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale;
Vector3 bottomLeft = transform.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;
Vector3 bottomRight = transform.forward * near + toRight - toTop;
bottomRight.Normalize();
bottomRight *= scale;
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);
material.SetMatrix("_FrustumCornersRay", frustumCorners);
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif
int index = 0;
if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
index = 0;
} else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
index = 1;
} else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
index = 2;
} else {
index = 3;
}
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
index = 3 - index;
#endif
o.interpolatedRay = _FrustumCornersRay[index];
return o;
}
最后在片元着色器中还原世界空间
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;
3.创造投影
我用球形的sdf将画面分为三个区域,球内,边界,球外,然后再对每个区域设置一些强度之类的值供差值使用就可以得到文章开头的效果了。
if(sdValue > _Edge)
{
finanlColor = lerp(originColor, _BarkGroundColor, _BackGroundIntensity);
}
else if(sdValue > 0)
{
finanlColor = lerp(_EdgeColor, _BarkGroundColor, sdValue / _Edge * _BackGroundIntensity);
}
else
{
finanlColor = half4(abs(worldPos.x / _ColorScale.x), abs(worldPos.y / _ColorScale.y), abs(worldPos.z / _ColorScale.z), 1);
finanlColor = lerp(originColor, finanlColor, _Intensity);
}
4.回到初心
其实最开始的时候我是想利用空间信息来给画面上点颜色,比如这样