您的位置:首页 > 移动开发 > Unity3D

Unity Shader入门精要学习笔记 - 第15章 使用噪声

2018-01-04 11:20 417 查看

Unity Shader入门精要学习笔记 - 第15章 使用噪声

本系列为UnityShader入门精要读书笔记总结,

原作者博客链接:http://blog.csdn.net/candycat1992/article/

书籍链接:http://product.dangdang.com/23972910.html

第15章 使用噪声

很多时候, 向规则的事物里添加一些“ 杂乱无章” 的效果往往会有意想不到的效果。 而这些

“ 杂乱无章” 的效果来源就是噪声。 在本章中, 我们将会学习如何使用噪声来模拟各种看似“ 神奇”

的特效。首先, 我们将使用一张噪声纹理来模拟火焰的消融效果。 然后,把噪声应用在模拟水面的波动上, 从而产生波光粼粼的视觉效果。 最后, 我们会回顾 13.3 节中实现的全局雾效, 并向其中添加噪声来模拟不均匀的飘渺雾效。

15.1 消融效果

消融效果常见于游戏中的角色死亡、地图烧毁等效果。这这些效果中,消融往往从不同的区域开始,并向看似随机的方向扩张,最后整个物体都将消失不见。我们将学习如何在Unity中实现这种效果。效果如下图所示。



要实现上图中的效果,原理非常简单,概括来说就是噪声纹理+透明度测试。我们使用对噪声纹理采样的结果和某个控制消融程度的阈值比较,如果小于阈值,就使用clip函数把它对应的像素裁剪掉,这些部分就对应了图中被“烧毁”的区域。而镂空区域边缘的烧焦效果则是将两种颜色混合,再用pow函数处理后,与原纹理颜色混合后的效果。

我们新建一个Unity Shader。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/Chapter15-MyDissolve"
{
Properties {
//控制消融程度
_BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0
//控制模拟烧焦效果时的线宽
_LineWidth("Burn Line Width", Range(0.0, 0.2)) = 0.1
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
//火焰边缘颜色
_BurnFirstColor("Burn First Color", Color) = (1, 0, 0, 1)
//火焰边缘颜色
_BurnSecondColor("Burn Second Color", Color) = (1, 0, 0, 1)
_BurnMap("Burn Map", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}

Pass {
Tags { "LightMode"="ForwardBase" }

//关闭剔除,正面和背面都会被渲染
//消融会导致裸露模型内部的构造, 如果只渲染正面会出现错误的结果
Cull Off

CGPROGRAM

#include "Lighting.cginc"
#include "AutoLight.cginc"

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

fixed _BurnAmount;
fixed _LineWidth;
sampler2D _MainTex;
sampler2D _BumpMap;
fixed4 _BurnFirstColor;
fixed4 _BurnSecondColor;
sampler2D _BurnMap;

float4 _MainTex_ST;
float4 _BumpMap_ST;
float4 _BurnMap_ST;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvBumpMap : TEXCOORD1;
float2 uvBurnMap : TEXCOORD2;
float3 lightDir : TEXCOORD3;
float3 worldPos : TEXCOORD4;
SHADOW_COORDS(5)
};

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//三张纹理对应的纹理坐标
o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap);
o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);

TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//阴影信息
TRANSFER_SHADOW(o);

return o;
}

fixed4 frag(v2f i) : SV_Target {
//对噪声纹理采样
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;
//将采样结果与_BurnAmount相减后 传递给clip
//小于0则会被剔除,不会显示到屏幕上
clip(burn.r - _BurnAmount);

float3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap));

fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb;

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
//计算了烧焦颜色 burnColor
fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t);
burnColor = pow(burnColor, 5);

UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));

return fixed4(finalColor, 1);
}

ENDCG
}

//用于投射阴影
Pass {
Tags { "LightMode" = "ShadowCaster" }

CGPROGRAM

#pragma vertex vert
#pragma fragment frag

#pragma multi_compile_shadowcaster

#include "UnityCG.cginc"

fixed _BurnAmount;
sampler2D _BurnMap;
float4 _BurnMap_ST;

struct v2f {
V2F_SHADOW_CASTER;
float2 uvBurnMap : TEXCOORD1;
};

v2f vert(appdata_base v) {
v2f o;

TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap);

return o;
}

fixed4 frag(v2f i) : SV_Target {
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb;

clip(burn.r - _BurnAmount);

SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
FallBack "Diffuse"
}


在本例中,我们使用的噪声纹理如下图所示。



15.2 水波效果

在模拟实时水面的过程中,我们往往也会使用噪声纹理。此时,噪声纹理通常会用作一个高度图,以不断修改水面的法线方向。为了模拟水不断流动的效果,我们会使用和时间相关的变量来对噪声纹理进行采样,当得到法线信息后,再进行正常的反射+折射计算,得到最后的水面波动效果。

我们将使用一个由噪声纹理得到的法线贴图,实现一个包含菲涅耳发射的水面效果,如下图所示。



我们在之前介绍过如何使用反射和折射来模拟一个透明玻璃的效果。我们要使用的Shader 和之前的类似。我们使用一张立方体纹理作为环境纹理,模拟反射。为了模拟折射效果,我们使用GrabPass来获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标进行偏移,再使用该坐标对渲染为进行屏幕采样,从而模拟近似的折射效果。水波的法线纹理是由一张噪声纹理生成而得,而且会随着时间变化不断平移,模拟波光粼粼的效果。除此之外,我们没有使用一个定值来混合反射和折射颜色,而是使用之前提到的菲涅耳系数来动态决定混合系数。我们使用如下公式来计算菲涅耳系数:



其中,v 和 n 分别对应了视角方向和法线方向。它们之间的夹角越小,fresnel 值越小,反射越弱,折射越强。菲涅耳系数还经常会用于边缘光照的计算中。

我们新建一个UnityShader。

Shader "Unlit/Chapter15-MyWaterWave"
{
Properties {
//控制水面颜色
_Color ("Main Color", Color) = (0, 0.15, 0.115, 1)
//水面波纹材质纹理
_MainTex ("Base (RGB)", 2D) = "white" {}
//由噪声纹理生成的法线纹理
_WaveMap ("Wave Map", 2D) = "bump" {}
//模拟反射的立方体纹理
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
_WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
//控制模拟折射时图像的扭曲程度
_Distortion ("Distortion", Range(0, 100)) = 10
}
SubShader {
//确保该物体渣染时, 其他所有不透明物体都已经被渲染
Tags { "Queue"="Transparent" "RenderType"="Opaque" }

//使用GrabPass 来获取屏幕图像
GrabPass { "_RefractionTex" }

Pass {
Tags { "LightMode"="ForwardBase" }

CGPROGRAM

#include "UnityCG.cginc"
#include "Lighting.cginc"

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};

v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//抓取屏幕图像的采样坐标
o.scrPos = ComputeGrabScreenPos(o.pos);

//计算了_MainTex和_BumpMap 的采样坐标,
//分别存储在一个float4类型变量的xy和zw分量中
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);

float3 worldPos = mul(_Object2World, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

//从切线空间到世界空间的变换矩阵  3 个坐标轴(x、y、z轴分别对应了切线、副切线和法线的方向)
//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);

return o;
}

fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);

//获得法线空间的切线
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);

//计算切线空间下的偏移
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;

//转换法线到世界空间
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
fixed3 reflDir = reflect(-viewDir, bump);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;

//菲尼尔节点
fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);

return fixed4(finalColor, 1);
}

ENDCG
}
}
// Do not cast shadow
FallBack Off
}


15.3 再谈全局雾效

我们之前讲到了如何使用深度纹理来实现一种基于屏幕后处理的全局雾效。我们由深度纹理重建每个像素在世界空间系下的位置,再使用一个基于高度的公式来计算雾效的混合系数,最后使用该系数来混合雾的颜色和原屏幕颜色。这种雾效是一个基于高度的均匀雾效,即在同一个高度上,雾的浓度是相同的,如下图左图所示。然而,一些时候我们希望可以模拟一种不均匀的雾效,同时让雾不断飘动,使雾看起来更加缥缈,如下图右图所示。而这就可通过使用一张噪声纹理来实现。



实现非常简单,绝大代码和之前的一样,我们只是添加了噪声相关的参数和属性,并在Shader 的片元着色器中对高度的计算添加了噪声影响。

我们在摄像机上添加脚本FogWithNoise.cs

public class FogWithNoise : PostEffectsBase {

public Shader fogShader;
private Material fogMaterial = null;

public Material material {
get {
fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
return fogMaterial;
}
}

//获取摄像机的相关参数
private Camera myCamera;
public Camera camera {
get {
if (myCamera == null) {
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}

private Transform myCameraTransform;
public Transform cameraTransform {
get {
if (myCameraTransform == null) {
myCameraTransform = camera.transform;
}

return myCameraTransform;
}
}

[Range(0.1f, 3.0f)]
public float fogDensity = 1.0f;
//fogDensity 用于控制雾的浓度
public Color fogColor = Color.white;
//fogColor 用于控制雾的颜色
public float fogStart = 0.0f;
public float fogEnd = 2.0f;
//fogStart 用于控制雾效的起始高度, fogEnd 用于控制雾效的终止高度
public Texture noiseTexture;
//noiseTexture 是我们使用的噪声纹理
[Range(-0.5f, 0.5f)]
public float fogXSpeed = 0.1f;

[Range(-0.5f, 0.5f)]
public float fogYSpeed = 0.1f;
//fogXSpeed 和 fogYSpeed 分别对应了噪声纹理在X和Y方向上的移动速度
[Range(0.0f, 3.0f)]
public float noiseAmount = 1.0f;
//noiseAmount 用于控制噪声程度
void OnEnable() {
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
}

void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
Matrix4x4 frustumCorners = Matrix4x4.identity;
//计算近裁剪平面的 4 个角对应的向量
float fov = camera.fieldOfView;
float near = camera.nearClipPlane;
float aspect = camera.aspect;

float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
Vector3 toRight = cameraTransform.right * halfHeight * aspect;
Vector3 toTop = cameraTransform.up * halfHeight;

Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
float scale = topLeft.magnitude / near;

topLeft.Normalize();
topLeft *= scale;

Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
topRight.Normalize();
topRight *= scale;

Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
bottomLeft.Normalize();
bottomLeft *= scale;

Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;
bottomRight.Normalize();
bottomRight *= scale;

//存储在一个矩阵类型的变量( frustumComers ) 中
frustumCorners.SetRow(0, bottomLeft);
frustumCorners.SetRow(1, bottomRight);
frustumCorners.SetRow(2, topRight);
frustumCorners.SetRow(3, topLeft);

material.SetMatrix("_FrustumCornersRay", frustumCorners);

material.SetFloat("_FogDensity", fogDensity);
material.SetColor("_FogColor", fogColor);
material.SetFloat("_FogStart", fogStart);
material.SetFloat("_FogEnd", fogEnd);

material.SetTexture("_NoiseTex", noiseTexture);
material.SetFloat("_FogXSpeed", fogXSpeed);
material.SetFloat("_FogYSpeed", fogYSpeed);
material.SetFloat("_NoiseAmount", noiseAmount);

Graphics.Blit (src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}


新建一个Unity Shader。

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/Chapter15-MyFogWithNoise"
{
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_FogDensity ("Fog Density", Float) = 1.0
_FogColor ("Fog Color", Color) = (1, 1, 1, 1)
_FogStart ("Fog Start", Float) = 0.0
_FogEnd ("Fog End", Float) = 1.0
_NoiseTex ("Noise Texture", 2D) = "white" {}
_FogXSpeed ("Fog Horizontal Speed", Float) = 0.1
_FogYSpeed ("Fog Vertical Speed", Float) = 0.1
_NoiseAmount ("Noise Amount", Float) = 1
}
SubShader {
CGINCLUDE

#include "UnityCG.cginc"

float4x4 _FrustumCornersRay;

sampler2D _MainTex;
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
half _FogDensity;
fixed4 _FogColor;
float _FogStart;
float _FogEnd;
sampler2D _NoiseTex;
half _FogXSpeed;
half _FogYSpeed;
half _NoiseAmount;

struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 uv_depth : TEXCOORD1;
float4 interpolatedRay : TEXCOORD2;
};

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;
}

fixed4 frag(v2f i) : SV_Target {
//先使用 SAMPLE_DEPTH_TEXTURE对深度纹理进行采样,
//再使用 LinearEyeDepth 得到视角空间下的线性深度值
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
//与 interpolatedRay相乘后再和世界空间下的摄像机位置相加, 即可得到世界空间下的位置
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;

//计算出当前噪声纹理的偏移量, 并据此对噪声纹理进行采样, 得到噪声值。
float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed);
float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount;

//实现基于高度的雾效模拟
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity * (1 + noise));
//截取到0-1范围之内
fixed4 finalColor = tex2D(_MainTex, i.uv);
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

return finalColor;
}

ENDCG

Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

ENDCG
}
}
FallBack Off
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: