图形噪声,是计算机图形学中一类随机算法,经常用来模拟自然界中的各种纹理材质,如云、山脉等,都是通过噪声算法模拟出来的。通过不同的噪声算法,作用在物体纹理和材质细节,我们可以模拟不同类型的材质。
以上节选自图形噪声
噪声纹理一般用于模拟看似随机的随机,使得随机更加自然合理,噪声纹理可以被看作是程序纹理的一种。
一、消融效果
Shader:
Shader "Custom/Test0"
{
Properties
{
_MainTex("主帖图",2D)="white"{}
_NormalTex("法线贴图",2D)="bump"{}
_BurnArea("烧毁面积",Range(0,1))=0.1
_BurnColor("烧毁颜色",Color)=(1,0,0,1)
_BurnColor1("烧焦颜色",Color)=(1,0,0,1)
_NoiseTex("噪声纹理",2D)="white"{}
}
SubShader
{
Pass
{
Cull off
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NormalTex;
float4 _NormalTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
fixed _BurnArea;
fixed4 _BurnColor;
fixed4 _BurnColor1;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
//切线不同于法线,需要用4个分量表示,因为切线的w属性用于指明副切线的方向
float4 tangent:TANGENT;
float4 texcoord:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 tangentLightDir :TEXCOORD0;
float3 tangentViewDir:TEXCOORD1;
//使用float4,前两个值存放基础纹理坐标,后两个值存放法线纹理坐标
float4 uv:TEXCOORD2;
float2 uv_Burn:TEXCOORD3;
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
TANGENT_SPACE_ROTATION;
o.tangentLightDir=mul(rotation,ObjSpaceLightDir(v.vertex));
o.tangentViewDir=mul(rotation,ObjSpaceViewDir(v.vertex));
o.uv.xy=TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw=TRANSFORM_TEX(v.texcoord,_NormalTex);
o.uv_Burn=TRANSFORM_TEX(v.texcoord,_NoiseTex);
return o;
}
fixed4 frag(v2f i):SV_Target
{
//剔除已经烧毁的区域
fixed3 noise_texResult=tex2D(_NoiseTex,i.uv_Burn);
clip(noise_texResult.r-_BurnArea);
//正常漫反射
half3 tangentNormal=UnpackNormal(tex2D(_NormalTex,i.uv.zw));
half3 tangentLightDir=normalize(i.tangentLightDir);
//纹理采样
fixed3 texResult=tex2D(_MainTex,i.uv.xy);
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.rgb;
fixed3 diffuse=_LightColor0*texResult
*saturate(dot(tangentLightDir,tangentNormal)*0.5+0.5);
//烧焦区域计算
fixed shaojiao=1-smoothstep(0,_BurnArea,noise_texResult.r-_BurnArea);
//烧焦颜色计算,感觉只用一个会更好调一些
fixed3 shaojiaoColor=lerp(_BurnColor,_BurnColor1,shaojiao);
shaojiaoColor=pow(shaojiaoColor,5);
//最终颜色计算
fixed3 color=lerp(ambient+diffuse,shaojiaoColor,shaojiao*step(0.0001,_BurnArea));
return fixed4(color,1.0);
}
ENDCG
}
}
}
效果还行。
二、水面波动
和之前玻璃的写法大差不差,主要区别就是反射用了菲涅尔,同时加入噪声纹理模拟水面的法线方向,营造出水面漂浮的感觉。
我们使用噪声纹理代替平常的法线贴图做计算,噪声纹理需要设置成从灰度生成法线。
相比于玻璃,我们需要让噪声纹理流动起来营造水面飘动,同时对噪声纹理进行采样用于计算 其次,使用菲涅尔反射控制折射和反射的比例。
计算反射时,必须在世界空间下进行,因为需要采样立方体纹理。
Shader "Custom/Test0"
{
Properties
{
_MainTex("主要纹理",2D)="white"{}
_NormalTex("法线贴图",2D)="bump"{}
_CubeMap("立方体纹理",Cube)="_Skybox"{}
_RefractLevel("折射程度",Range(0,100))=50
_WaterSpeedX("水流X速度",float)=0.01
_WaterSpeedY("水流Y速度",float)=0.01
}
SubShader
{
Tags
{
"Queue"="Transparent" "RenderType"="Opaque"
}
GrabPass
{
"_RefractionTex"
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NormalTex;
float4 _NormalTex_ST;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
samplerCUBE _CubeMap;
fixed _RefractLevel;
fixed _WaterSpeedX;
fixed _WaterSpeedY;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
float2 texcoord:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD0;
float4 scrPos:TEXCOORD1;
float4 TtoW0:TEXCOORD2;
float4 TtoW1:TEXCOORD3;
float4 TtoW2:TEXCOORD4;
float3 worldNormal:TEXCOORD5;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _NormalTex);
//矩阵计算
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
o.worldNormal=worldNormal;
return o;
}
fixed4 frag(v2f i):SV_Target
{
float2 speed=_Time.y*float2(_WaterSpeedX*0.1,_WaterSpeedY*0.1);
//营造出浮动的感觉
fixed3 tangentNormal1 = UnpackNormal(tex2D(_NormalTex, i.uv.zw+speed));
fixed3 tangentNormal2 = UnpackNormal(tex2D(_NormalTex, i.uv.zw-speed));
fixed3 tangentNormal = normalize(tangentNormal1+tangentNormal2);
//折射部分
fixed2 offset = tangentNormal.xy * _RefractLevel * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset + i.scrPos.xy;
fixed3 refract = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w);
//反射部分
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 worldNormal = normalize(half3(dot(i.TtoW0.xyz, tangentNormal)
, dot(i.TtoW1.xyz, tangentNormal),
dot(i.TtoW2.xyz, tangentNormal)));
fixed3 reflectionDir = reflect(-worldViewDir,worldNormal);
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflection = texCUBE(_CubeMap, reflectionDir) * texColor;//*_MainColor;
//如果和书上一样用噪声纹理的法线,效果有点差,无法实现书中示例的样子
//不清楚是不是版本问题
fixed fresnel = pow(1 - saturate(dot(worldViewDir, i.worldNormal)), 4);
//最终结果
fixed3 finalColor = reflection * fresnel + refract *(1-fresnel);
return fixed4(finalColor, 1);
}
ENDCG
}
}
}
结果也是怪怪的感觉,尤其是这个噪声的表现,不知道是不是个人写法问题什么的,而且书中的写法在我的2020版本中也是有问题,无法表现的和书中实例一样 。而且还有个问题,离水面越近,扰动就越剧烈,想了想好像是视角问题,拉近水面的时候噪声的偏扰占比加大,就和大风中离的远树摇晃的不狠,离的近看感觉挺晃的,应该是这个道理。
三、非均匀雾效
脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class RenderImage : MonoBehaviour
{
public Shader shader;
private Camera _camera;
private Material _material;
[Header("雾强度")] public float fogStrength = 0.5f;
[Header("雾颜色")] public Color fogColor = Color.white;
[Header("雾起始高度")] public float fogStartDis = -3;
[Header("雾结束高度")] public float fogEndDis = 2;
[Header("启用雾")] public bool key;
[Header("噪声系数")] public float noiseStrength = 1;
public float noiseSpeedX;
public float noiseSpeedY;
// Start is called before the first frame update
void Awake()
{
if (shader == null)
{
Debug.Log("Shader为空");
}
else
{
_camera = GetComponent<Camera>();
_camera.depthTextureMode |= DepthTextureMode.Depth;
//_material = new Material(shader);
}
}
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (!key)
{
_material = null;
}
else
{
_material = new Material(shader);
}
if (_material != null)
{
//计算四个向量的前期准备
float temp = _camera.nearClipPlane * Mathf.Tan(_camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
Vector3 up = _camera.transform.up * temp;
Vector3 right = _camera.transform.right * _camera.aspect * temp;
//开始计算四个向量,但此时向量只有方向信息
var nearClipPlane = _camera.nearClipPlane;
Vector3 temp1 = _camera.transform.forward * nearClipPlane;
//左上
Vector3 LT = temp1 + up - right;
//左下
Vector3 LB = temp1 - up - right;
//右上
Vector3 RT = temp1 + up + right;
//右下
Vector3 RB = temp1 - up + right;
//计算真正的深度比例,送给片元着色器用于得到准确的深度
//知道一个像素的深度,我们可以根据相似三角形计算比例
float scale = LT.magnitude / nearClipPlane;
//对四个向量进行深度比例的处理,这样的向量就包含了真正的距离和方向
LT.Normalize();
LT *= scale;
LB.Normalize();
LB *= scale;
RT.Normalize();
RT *= scale;
RB.Normalize();
RB *= scale;
//我们利用一个矩阵存储四个向量,先初始化为单位矩阵
Matrix4x4 temp2 = Matrix4x4.identity;
temp2.SetRow(0, LT);
temp2.SetRow(1, LB);
temp2.SetRow(2, RT);
temp2.SetRow(3, RB);
//材质值传递
_material.SetMatrix("_CornerVector", temp2);
_material.SetFloat("_FogStrenth", fogStrength);
_material.SetColor("_FogColor", fogColor);
_material.SetFloat("_FogStart", fogStartDis);
_material.SetFloat("_FogEnd", fogEndDis);
_material.SetFloat("_NoiseSpeedX", noiseSpeedX);
_material.SetFloat("_NoiseSpeedY", noiseSpeedY);
_material.SetFloat("_NoiseStrngth", noiseStrength);
Graphics.Blit(src, dest, _material);
}
else
{
Graphics.Blit(src, dest);
}
}
}
Shader:
Shader "Custom/Test0"
{
Properties
{
//不写这句话就是灰,真的搞不懂Unity开发者写的什么代码,试了半小时给试出来了
//偏偏别的都能过,就纹理必须写。。。
_MainTex("主帖图",2D)="white"{}
_NoiseTex("噪声贴图",2D)="white"{}
}
SubShader
{
Pass
{
Tags
{
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _NoiseTex;
float4x4 _CornerVector;
float _FogStrenth;
float4 _FogColor;
float _FogStart;
float _FogEnd;
float _NoiseSpeedX;
float _NoiseSpeedY;
float _NoiseStrngth;
//声明获取深度纹理
sampler2D _CameraDepthTexture;
struct a2v
{
float4 vertex:POSITION;
float4 texcoord:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
float4 dir:TEXCOORD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
int index;
//关系对应好
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 = 3;
}
else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5)
{
//右上
index = 2;
}
else
{
//左上
index = 0;
}
o.dir = _CornerVector[index];
return o;
}
fixed4 frag(v2f i):SV_Target
{
//解析深度
float depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv));
//世界坐标计算
float3 worldPos = _WorldSpaceCameraPos + depth * i.dir;
//噪声计算
float2 speed=_Time.y*float2(_NoiseSpeedX,_NoiseSpeedY);
float noise=(tex2D(_NoiseTex,i.uv+speed).r-0.5)*_NoiseStrngth;
//雾效公式,(高度雾,参数是y
//这种写法如果y值越低,雾越浓,即便是y值超过了起始点
float fog = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
//在后面再乘一个值把上面的影响消掉
//但为了防止对式子结果产生影响,雾的起始高度要够低
fog = saturate(fog* _FogStrenth*(1+noise)) ; //*saturate(worldPos.y-_FogStart);
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb = (1 - fog) * color.rgb + fog * _FogColor.rgb; // lerp(color.rgb, _FogColor.rgb, fog);
return color;
}
ENDCG
}
}
}
结果还行,在采样上加个时间变化雾就可以飘着移动了。