unity地形渲染升级:预计算接触阴影
效果展示因为地形纹理不会绕y轴旋转而当前项目是静态平行光,针对这些自由度的限制 想了这套性能最高的预计算可见角度的地形自阴影渲染方案(1次采样).效果又比ue4实时接触阴影或曲面细分+shadowmap 柔和精确.先放最终效果图.
普通地形效果
本方案效果
普通地形效果
本地方案效果
普通地形效果
本方案效果
地形阴影动态效果
https://www.zhihu.com/video/1511388249272983552
同一个素材 ue4下 阴影更硬,这是因为ue4的接触阴影采用非常有限的步进采样导致 通用性更好,针对性地形不够好.
ue4 下 接触阴影
脑洞过程
最近开始升级地形系统阴影部分,一般成熟的做法是用接触阴影,而且我之前已经在项目中实现了接触阴影.但地形不能直接用,因为地形mesh本身深度变化不能体现地表高度图里像素级的变化,所以一般有2种方案配套,1用曲面细分增加深度图变化精度,2通过高度图做depthoffset,让屏幕空间的深度图产生细节凹凸,这样接触阴影在 查询的时候会按遮挡处理产生细节阴影.做了下效果确实也不错,但曲面细分 或 接触阴影的12次以上采样查询性能都是 十倍与本方案开销.所以写完demo就开始思考性能更好的技术方案.以下是曲面细分+shadowmap效果,实际上shadowmap的精度会更差一些,这里的bias distance等 都完全适配近处地形.而接触阴影效果因为有限的步进导致会更差一些.具体效果可看之前的文章.
普通效果
曲面细分+shadowmap效果
让我最后决定想一种新方案的 根本原因是这个.因为我地形本身有其他图有免费a通道可用,所以性能上0开销.表里可以看到 不开曲面细分效果不行 开了性能不行.因为是3080 所以不要有性能开销不大的错觉,按增量看非常大.
方案思想
这个想法受到sdf启发,另外是考虑到地形贴图不可绕y旋转,且xz倾斜角度也不会超过90度且,大部分处于水平等因素.静态场景可以预先知道 太阳在哪个方位,只是因为地形会倾斜所以相对仰角会变化(其实方位角也会小幅度改变但不易察觉).所以我们给每个像素记录下 这个点在阳光方位上的 遮挡角度即可.用cube 代替 每个像素不同高度图里的高度信息.可以看到,如果与法线记录夹角,分别是90度,45度,60度,50度60度,70度(目测的大概数值).运行时只要判断 平行光相对于地面法线的 夹角大小即可.大于记录的值就为阴影,否则不是阴影.当然可以用smoothstep做上下10度范围的软阴影.下面是每个像素追踪查询阳光方位200个像素的遮挡统计(从3开始,是因为挨着太近,tan函数的分母太接近0 实际效果误差巨大 用atan2反算一样有这问题).因为高度图1的单位具体多少米没法确定 所以和一个像素具体跨度是多少米之间的关系加个缩放值,具体要根据资源测试.一定要用这样的computeshader来算,cpu算一张2048图需要5分钟,这个1秒内完成.
高度图遮挡角度记录
每个像素遮挡角度的计算
渲染的shader 核心部分也很简单,光线转到对象空间,如果是地形 需要自己算每个四边形网格的切线空间.只考虑了一侧的阴影所以 可以用 localL.y 大小算角度大小.
完整代码
StaticSelfShadowGPU.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class StaticSelfShadowGPU : MonoBehaviour {
public Shader renderShader;
public ComputeShader cs_calculateShadowRotation;
public Light shadowLight;
void Start () {
Vector3 v = Vector3.Cross(new Vector3(1,1,0).normalized, Vector3.up).normalized;
print(v);
v= Vector3.Cross( v, new Vector3(1, 1, 0).normalized);
print(v);
}
void createSelfShadowForSceneRenders() {
foreach (var item in FindObjectsOfType<Renderer>())
{
if (item.sharedMaterial != null && item.sharedMaterial.shader == renderShader) {
createSelfShadow(item.sharedMaterial);
}
}
}
void createSelfShadow(Material material) {
RenderTexture selfShadow = material.GetTexture(&#34;_SelfShadow&#34;) as RenderTexture;
var heightTex = material.GetTexture(&#34;_HeightTex&#34;) ;
if (selfShadow != null) {
selfShadow.Release();
}
selfShadow = new RenderTexture(heightTex.width, heightTex.height,0, RenderTextureFormat.R8, RenderTextureReadWrite.Linear);
material.SetTexture(&#34;_SelfShadow&#34;, selfShadow);
selfShadow.wrapMode = TextureWrapMode.Repeat;
selfShadow.enableRandomWrite = true;
selfShadow.autoGenerateMips = false;
selfShadow.useMipMap = true;
selfShadow.Create();
cs_calculateShadowRotation.SetTexture(0,&#34;Result&#34;, selfShadow);
cs_calculateShadowRotation.SetTexture(0, &#34;HeightMap&#34;, heightTex);
cs_calculateShadowRotation.SetInt(&#34;HeightMapWidth&#34;, heightTex.width);
cs_calculateShadowRotation.SetVector(&#34;lightDir&#34;, shadowLight.transform.forward);
cs_calculateShadowRotation.Dispatch(0, heightTex.width / 8, heightTex.height / 8, 1);
selfShadow.GenerateMips();
}
}
StaticSelfShadow.compute
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
RWTexture2D<float4> Result;
Texture2D<float4> HeightMap;
int HeightMapWidth;
float4 lightDir;
#define PI3.1415926F
void CSMain (uint3 id : SV_DispatchThreadID)
{
float hBase = HeightMap.r;
float maxRot = 0;
float2 dir2D = normalize(lightDir.xz);
for (int i = 3; i < 200; i++)
{
float h = HeightMap[int2(round(id.x + i* dir2D.x+ HeightMapWidth) % HeightMapWidth,
round(id.y + i * dir2D.y + HeightMapWidth) % HeightMapWidth)].r;
maxRot =max(maxRot, atan2(h - hBase, i / 250.0f));
}
Result = float4(maxRot / (PI / 2), 0, 0, 1);
}
StaticSelfShadow.shader
// Upgrade NOTE: replaced &#39;_Object2World&#39; with &#39;unity_ObjectToWorld&#39;
Shader &#34;Custom/StaticSelfShadow&#34; {
Properties {
_Color (&#34;Color&#34;, Color) = (1,1,1,1)
_MainTex (&#34;Albedo (RGB)&#34;, 2D) = &#34;white&#34; {}
_GlossTex (&#34;_GlossTex&#34;, 2D) = &#34;white&#34; {}
_HeightTex (&#34;Height&#34;, 2D) = &#34;black&#34; {}
_BumpTex (&#34;bump&#34;, 2D) = &#34;bump&#34; {}
_Glossiness (&#34;Smoothness&#34;, Range(0,1)) = 0.5
_AOTex(&#34;AO&#34;, 2D) = &#34;white&#34; {}
_UseSelfShadow (&#34;UseSelfShadow&#34;,float) = 0.0
_UseNormal (&#34;UseNormal&#34;,float) = 0.0
_UseAlphaForSmooth (&#34;UseAlphaForSmooth&#34;,float) = 0.0
_InvertGloss (&#34;InvertGloss&#34;,float) = 0.0
}
SubShader {
Tags { &#34;RenderType&#34;=&#34;Opaque&#34; }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard2 vertex:vert fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
#include &#34;UnityPBSLighting.cginc&#34;
sampler2D _MainTex;
sampler2D_SelfShadow;
sampler2D _BumpTex;
sampler2D _AOTex;
sampler2D _GlossTex;
float _UseSelfShadow;
float _UseNormal;
float _UseAlphaForSmooth;
float _InvertGloss;
struct Input {
float2 uv_MainTex;
float3 wNormal;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
struct SurfaceOutputStandard2
{
fixed3 Albedo; // base (diffuse or specular) color
fixed3 Normal; // tangent space normal, if written
fixed3 Emission;
half3 faceNormal;
half Metallic; // 0=non-metal, 1=metal
// Smoothness is the user facing name, it should be perceptual smoothness but user should not have to deal with it.
// Everywhere in the code you meet smoothness it is perceptual smoothness
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
fixed selfShadowRot;
};
inline half4 LightingStandard2(SurfaceOutputStandard2 s, half3 viewDir, UnityGI gi)
{
s.Normal = normalize(s.Normal);
float3 faceNormal = s.faceNormal;
half oneMinusReflectivity;
half3 specColor;
s.Albedo = DiffuseAndSpecularFromMetallic(s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
// shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
// this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
half outputAlpha;
if (_UseSelfShadow&& s.selfShadowRot >0.01) {
float3 localL = UnityWorldToObjectDir(gi.light.dir);
float lrot = atan2(localL.y, sqrt(1- localL.y* localL.y))*180/UNITY_PI;
gi.light.color *= 1 - smoothstep(lrot-20, lrot+10, s.selfShadowRot * 90);
}
s.Albedo = PreMultiplyAlpha(s.Albedo, s.Alpha, oneMinusReflectivity, /*out*/ outputAlpha);
half4 c = UNITY_BRDF_PBS(s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
c.a = outputAlpha;
return c;
}
inline void LightingStandard2_GI(
SurfaceOutputStandard2 s,
UnityGIInput data,
inout UnityGI gi)
{
#if defined(UNITY_PASS_DEFERRED) && UNITY_ENABLE_REFLECTION_BUFFERS
gi = UnityGlobalIllumination(data, s.Occlusion, s.Normal);
#else
Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.Smoothness, data.worldViewDir, s.Normal, lerp(unity_ColorSpaceDielectricSpec.rgb, s.Albedo, s.Metallic));
gi = UnityGlobalIllumination(data, s.Occlusion, s.Normal, g);
#endif
}
// Add instancing support for this shader. You need to check &#39;Enable Instancing&#39; on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_CBUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_CBUFFER_END
void vert(inout appdata_full v, out Input data)
{
UNITY_INITIALIZE_OUTPUT(Input, data);
data.wNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
}
void surf (Input IN, inout SurfaceOutputStandard2 o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
half4data= tex2D (_SelfShadow, IN.uv_MainTex);
o.selfShadowRot = data.r;
o.faceNormal = IN.wNormal;
o.Albedo = c;
o.Metallic = _Metallic;
o.Smoothness = tex2D(_GlossTex, IN.uv_MainTex)*_Glossiness;
if (_UseAlphaForSmooth > 0) {
o.Smoothness = c.a;
}
if (_InvertGloss > 0) o.Smoothness = 1 - o.Smoothness;
if (_UseNormal > 0) {
o.Normal = UnpackNormal(tex2D(_BumpTex, IN.uv_MainTex));
}
else {
o.Normal = half3(0, 0, 1);
}
o.Occlusion = tex2D(_AOTex, IN.uv_MainTex);
}
ENDCG
}
FallBack &#34;Diffuse&#34;
}<hr/>2022/05/24 更新
上面是用plane 测试的结果,如果要用到地形shader里,需要做光线到切线空间转换,比较容易出错我就记录下来,还有 computeshader里方向也要取反.
地形shader 落地效果 牛牛牛,下次把树也加高度图升级下[酷] 以后可以贴图可以专门拿个通道做SDF细节阴影图,把高度图替代掉 好的树也不难 因为只绕y轴转 不愧是大佬[赞] 本移动平台用户看到200觉得可以先羡慕一下。[捂脸] 我找时间弄个低配的感受一下
页:
[1]