|
一.前言
几个月前网上找柏林噪声资料后自己想用C#实现一个生成柏林噪声图的应用,然后怎么弄都出不来想要的结果,感觉网上的代码拉下来试感觉都是对的,对比一下我代码也没啥问题,就搁置在那了。
现在做雾效的时候看到用perlinnoise实现的于是又重新研究了一遍(指return个float2结果函数体返回值写成了float,unity只报了warning没有理浪费我大量的美好时光),这回对柏林噪声有了更深刻的理解。在这里我分享一下研究心得,希望大家研究这块的时候少走些弯路。
二.简要介绍和体会
1.perlinnoise
要生成二维柏林噪声,先想象一个三维空间,xy是二维坐标,z轴是噪声值。
柏林噪声本质上是将xy平面分成n*m个方格(通常方格大小为单位1的正方形),则噪声图以如下方式生成:
①对于每个方格顶点,手动给定一个噪声值(z值,后文以height特指方格顶点的z值)以及梯度值(grad值,表示方向导数在此处取最大值);
(注意方格顶点不一定与像素点重合,因此方格顶点是虚拟存在的)
②对于位于方格内的像素点,每个像素点根据其位于方格内的坐标(uv正好在0~1之间,通常用像素位置取小数部分表示,方便描述起见称之为方格uv)计算出周围4个方格顶点在此处的噪声值grad0,grad1,grad2,grad3
(根据计算公式z=k(x-x0)+k(y-y0)+height=dot(方格顶点的梯度值,(像素点位置-方格顶点位置)+height)
③用周围四个顶点对此像素点进行双线性插值(在进行双线性插值前通常对方格uv用缓和曲线进行映射使结果图在放个顶点处的变化更柔和)最大的问题在于如何控制最终生成的噪声值的区间(比如落在[0,1]之间)以及概率密度分布。这个问题要求的值受8个变量影响(4个顶点的高度值,梯度值),而且自变量有两个(被采样的像素点坐标xy),求解不能,只能按照经验来了。我参考了一下各路代码,发现有以下处理措施(并不能保证能均匀分布在[0,1]之间):
·height取0。在点积的时候将点积结果/2+1,目的是将点积的[-1,1]映射到[0,1](因为点积范围其实不是[-1,1]实际结果发现颜色基本集中在80-160,没有特别黑特别白的地方看着倒挺舒服)(参考https://blog.csdn.net/o83290102o5/article/details/117426002)
·height取高一点的值,不是0就行。这样做明显有黑色区域,有些点实际上仍小于0(参考https://zhuanlan.zhihu.com/p/360235233)
实际做的时候也可以两个方案都参考一下,毕竟图形学第一定律,看起来对的就是对的嘛。
2.fbm(分型布朗运动)化perlin noise
fbm本质上是不同频率和幅度的noise的叠加
代码如下
_Octaves表示迭代次数
_Frequency表示噪声频率
_Amplitude 表示噪声幅度
_Lacunarity表示每次累加噪声时,噪声的频率变化
_Gain 表示每次累加噪声时,噪声的幅度变化
这里再作一点补充:
通常amplitude取0.5,gain取0.5,这意味着当迭代次数增大时,weight趋向于1,这也是有些代码没有在最后除于总权值的原因。如果amplitude和gain取其他值的话,那么除于总权值(幅度amplitude的和)可以保证fbm结果区间和被fbm化的noise的区间一致
最后给上完整的shader代码
Shader "Custom/Fog2.0"{
Properties{
_MainTex("主纹理", 2D) = "white" {}
_Color("雾颜色", color) = (1,1,1,1)
_FogPower("雾强度", Range(0,1)) = 0.5
_FogVisibility("雾密度/可见性", Range(0,1)) = 0.2
_Octaves("分型次数",int) = 4
_Frequency("采样频率",float) = 1.0
_Amplitude("幅度",float) = 0.5
_Lacunarity("频率增加倍数",float) = 2.0
_Gain("幅度增加倍数",float) = 0.5
_Speed("速度",float) = 3.0
_NoiseUnitCount("方格个数/噪声密度",float) = 5.0
}
SubShader{
Pass{
Tags { "LightMode" = "UniversalForward"}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _FogPower;
float4 _Color;
int _Octaves;
float _Frequency;
float _Amplitude;
float _Lacunarity;
float _Gain;
float _FogVisibility;
float _Speed;
float _NoiseUnitCount;
CBUFFER_END
TEXTURE2D (_MainTex);
SAMPLER(sampler_MainTex);
//sampler2D _MainTex;
struct a2v {
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
half2 rand(float2 p){
float theta = 52 * sin(p.x * 6+ p.y * 5);//随便编一个
//float theta = sin(666 + p.x * 5678 + p.y * 1234) * 4321;
return half2(cos(theta),sin(theta));//单位向量
}
float2 smoothLerp(float2 x){
return x * x * x * (x * (x * 6 - 15) + 10);
}
float perlinNoise(float2 x)
{
float2 i = floor(x);
float2 uv = frac(x);
float2 posa = uv;
float2 posb = uv - float2(1.0, 0.0);
float2 posc = uv - float2(0.0, 1.0);
float2 posd = uv - float2(1.0, 1.0);
float2 grada = rand(i);
float2 gradb = rand(i + float2(1.0, 0.0));
float2 gradc = rand(i + float2(0.0, 1.0));
float2 gradd = rand(i + float2(1.0, 1.0));
float dot1=dot(posa,grada) / 2 + 0.5f;
float dot2=dot(posb,gradb) / 2 + 0.5f;
float dot3=dot(posc,gradc) / 2 + 0.5f;
float dot4=dot(posd,gradd) / 2 + 0.5f;
float2 u = smoothLerp(uv);
float x1 = lerp(dot1,dot2,u.x);
float x2 = lerp(dot3,dot4,u.x);
return lerp(x1,x2,u.y);
}
float fbm(float2 x)
{
float value = 0;
float weight = 0;
float frequency = _Frequency;
float amplitude = _Amplitude;
for(int i=0;i<_Octaves;++i)
{
value += perlinNoise(x * frequency) * amplitude;
frequency *= _Lacunarity;
weight +=amplitude;
amplitude *= _Gain;
}
return value/weight;
}
v2f vert(a2v v)
{
v2f o;
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
o.uv= TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
half4 frag(v2f i) : SV_Target
{
//float3 mainColor = tex2D(_MainTex,i.uv).rgb;
float3 mainColor = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.uv).rgb;
float noise = perlinNoise(i.uv*_NoiseUnitCount+_Time.x*_Speed);//噪声函数
float3 fogColor = _Color.rgb*noise*_FogPower;
float3 final = lerp(mainColor,fogColor,_FogVisibility);
return float4(final,1.0f);
}
ENDHLSL
}
}
}最终效果:
https://www.zhihu.com/video/1436124454967898112
参考链接
讲完shader顺便简单介绍urp中如何对屏幕进行后处理(具体介绍看最后的链接,我介绍起来会很乱所以不再赘述)
1.新建Rengderer Feature。unity会在这个脚本里同时创建Rengderer Feature(用于将pass传入渲染流程的特定阶段中)和Rengderer Pass(具体的渲染命令,使用command buffer操作),为了结构清晰可以把Rengderer Pass另放一个脚本
2.编写Rengderer Feature类。继承自criptableRendererFeature,最主要的功能是将pass提交到渲染队列中,其次可以在inspector面板中暴露一些参数用于传递给pass,再由pass对material进行参数设置,参考代码如下:
public class FogFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Setting {
public Material fogMaterial;
public RenderPassEvent renderPassEvent;
public string tag;
public FilterMode filterMode;
}
public Setting setting;
FogRenderPass m_FogRenderPass;
public override void Create()
{
m_FogRenderPass = new FogRenderPass(setting.renderPassEvent,setting.fogMaterial,setting.tag,setting.filterMode);
}
// Here you can inject one or multiple render passes in the renderer.
// This method is called when setting up the renderer once per-camera.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
//获取相机的主纹理
m_FogRenderPass.Setup(renderer.cameraColorTarget);
renderer.EnqueuePass(m_FogRenderPass);
}
}
3.编写Renderer Pass。继承自ScriptableRenderPass,在构造函数进行初始化,然后在Execute中调用Command进行绘制:将m_cameraColorIdentifier摄像机颜色纹理用m_fogMaterial材质(在asset创建材质并用刚写的shader拖拽赋值)绘制到临时纹理上,再用临时纹理绘制回摄像机颜色纹理上(不能对同一个纹理又读又写,因此需要申请一个临时纹理,用完就扔)
class FogRenderPass : ScriptableRenderPass
{
private string m_commandBufferTag;//用于在frame debugger中与其他标签区分
private Material m_fogMaterial;
private RenderTargetIdentifier m_cameraColorIdentifier;
private FilterMode filterMode;
private RenderTargetHandle m_temp;
public FogRenderPass(RenderPassEvent evt, Material material, string tag,FilterMode filterMode)
{
renderPassEvent = evt;
m_commandBufferTag = tag;
this.filterMode = filterMode;
if (material == null)
{
Debug.LogWarningFormat(&#34;urp pp&#39;s Fog Material is missing,{0} wouldn&#39;t be executed&#34;, GetType().Name);
return;
}
m_fogMaterial = material;
m_temp.Init(&#34;temp&#34;);
}
public void Setup(RenderTargetIdentifier cameraColorTarget)
{
this.m_cameraColorIdentifier = cameraColorTarget;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (!renderingData.cameraData.postProcessEnabled)
{
return;
}
var cmd = CommandBufferPool.Get(m_commandBufferTag);
RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
cmd.GetTemporaryRT(m_temp.id, cameraTargetDescriptor,filterMode);
cmd.Blit(m_cameraColorIdentifier, m_temp.Identifier(), m_fogMaterial);
cmd.Blit(m_temp.Identifier(), m_cameraColorIdentifier);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
cmd.ReleaseTemporaryRT(m_temp.id);
}
}
4.最后在asset_renderer里添加自定义的Rengderer Feature并设置参数
添加刚写的Rengderer Feature
参考链接 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|