结合上篇的理论基础,这篇文章我们将体积雾要考虑的因素一一实现
密度
散射
灯光阴影
可喜的是,很多数值我并没有写死,暴露出来,能调出很多意想不到的效果
texture实际没有用到,可以其实可以用它来做一些光影的蒙版,但每一次投射都需要矩阵变换很费,所以先放着没做
这些效果很多只需要一次两次的ray cast,所以细心分离,完全可以用在普通的shader中做特效
中毒效果(2层采样)
烟雾性毒圈(也很省)
冰墙
这个我也不知道叫什么好
总之再开发性还是很高的
通过RayMarching的方法,每投射一步算一次区一次雾的属性,继续投射前进知道遇到场景物体或投射最大值停止,将此射线所有点的属性按一定方式叠加,为此点的雾效果;
每次投射将浓度、散射、透光强度、光影等因素考虑进去;浓度通过世界坐标采样3Dtexture、散射、透光强度按之前的公式计算、光影向每光源方向做积分运算(此篇简化只做一次)分析shader主要部分(其他部分见前几篇):
1.投射方式、取舍与优化
2.采样方式及高度、noise密度、移动设置
3.云雾效果光影实现
1.投射方式、取舍与优化
投射方式在控制参数中添加:_startDistance/_endDistance/_stepNum
每次投射的间隙是相等的:(_endDistance-_startDistance)/_stepNum
带来的问题:与场景的交接除有明显痕迹
应对措施:参考shadertoy 上的iq的3D cloud
将每次步进的距离设为 t=max(0.05,0.02*t); 等于暴力的加强近出的计算量;但这对于游戏来说显然是太费了
每条射线所到达的深度又是不一样的,所以一般的立方体限制算法行不通
所以我采用虚化射线末端边缘
//消除层投射与场景的硬结边1
float alpha=1.0;
if(t>=rz-10){
alpha = smoothstep(0,10,rz-t);
}
for(int i =0;i<_stepNum;i++){
p = ro + t *rd;
//各种限制节省投射次数------------------------------------
if(light.a>0.99||t>=rz){
break;
}
if(rd.y>0 && p.y>_maxHeight){
break;
}
//消除层投射与场景的硬结边1
float alpha=1.0;
if(t>=rz-10){
alpha = smoothstep(0,10,rz-t);
}
//每个采样点的各种限制------------------------------------
float den = getDen(p);
//光照计算--------------
"略"
}
t+=stepSize;
}
2.采样及雾高度、密度、移动设置
得到密度、抽出_minCloud/_maxCloud 控制雾量
//得到密度
float getDen(float3 p ){
float den = FBM(p , _cloudFra , _maxHeight,_cloudSize);
den = smoothstep(_minCloud , _maxCloud,den);
return den;
}
采样函数 的高度、密度、移动
float FBM( float3 p,float iterNum,float _maxHeight,float _cloudSize)
{
//设置高度
float alpha = smoothstep(_maxHeight , _maxHeight-5, p.y);
//调整cloud大小
p *= _cloudSize/5;
p+= float3(1,1,3) * _Time.y * _moveSpeed;
float f = 0.0;
float s = 0.5;
float s2 = 2.00;
float sum = 0.0;
for(int i = 0;i< iterNum;i++){
f += s * VNoise( p );
p *=s2;
sum+=s;
s*= 0.5;s2+=0.01;
}
return (f/sum) * alpha;
}
3.云雾的光影实现
结合理论篇
单说的点:
getDen(p-1*_lightDir) 得到该处雾向光源方向前进1个单位处 的雾的浓度
透光比与理论篇有些出入,我是实际效果改了下,任意发挥吧
//每个采样点的各种限制------------------------------------
float den = getDen(p);
if(den>0.01){
//此处的云是否被灯源方向的云遮挡
float dif = clamp((den - getDen(p-1*_lightDir))/0.6, 0.0 , 1.0 );
float3 lin = float3(0.65,0.7,0.75)*1.4 + _lightColor * dif * _lightIntensity;
//浓度做Alpha
float4 col = float4(_fogColor,den);
col.xyz*=lin;
//透光比 距离与浓度
col.xyz = lerp(col.xyz , sceneCol.xyz , exp(-t*t));
//消除层投射与场景的硬结边2
col.a *= alpha ;
//添加散射衰减
float cosTheta=dot(_lightDir,-rd);
float result = 1/(4*3.14)*(1-pow(_g,2))/pow(1+pow(_g,2)-2*_g*cosTheta,1.5);
//此层的颜色 * den * 散射 * 强度控制
col.xyz *= col.a * result * _densityInstence;
light = light + col*(1.0-light.a);
}
脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]
public class VolumeFog : MonoBehaviour
{
public Light dirLight=null;
private Vector4 lightDir;
public Texture lightShadow=null;
//fog
public Shader fogShader=null;
private Material fogMaterial=null;
//cast
public Color fogColor=Color.yellow;
public Color LightColor=Color.yellow;
public float LightIntensity=1.0f;
public float cloudSize=1.0f;
[Range(0,1)]
public float moveSpeed=0.1f;
public float startDistance=2;
public float endDistance=300;
public int stepNum = 100;
public float maxHeight=50;
[Range(0,10)]
public float densityInstence=0.6f;
public Vector2 cloudDenAdjust = new Vector2(1.0f,1.0f);
[Range(2,5)]
public int cloudFra=3;
public float g = 0.6f;
//camera
private Camera mainCamera=null;
private void OnEnable() {
mainCamera = this.GetComponent();
if(dirLight!=null&&fogShader!=null&&fogShader.isSupported&&mainCamera!=null){
lightDir=dirLight.transform.forward;
fogMaterial=new Material(fogShader);
//让摄像机产生深度纹理
mainCamera.depthTextureMode |=DepthTextureMode.Depth;
}else
{
enabled = false;
}
}
private void OnDisable() {
if(mainCamera){
mainCamera=null;
}
if(fogMaterial){
fogMaterial=null;
}
}
private void SetRay(){
Matrix4x4 fourPoint=Matrix4x4.identity; //角度与弧度的转换 Π 与 0
float fov = mainCamera.fieldOfView;
float near =mainCamera.nearClipPlane;
float aspect=mainCamera.aspect;
float halfHeight = near* Mathf.Tan(fov*0.5f*Mathf.Deg2Rad);
float halfRight = halfHeight * aspect;
Vector3 toHeight = mainCamera.transform.up * halfHeight;
Vector3 toRight = mainCamera.transform.right * halfRight;
Vector3 topLeft = mainCamera.transform.forward*near + toHeight - toRight;
float scale= topLeft.magnitude/near;
topLeft*= scale;
Vector3 topRight=mainCamera.transform.forward*near +toHeight +toRight;
topRight*=scale;
Vector3 bottomLeft=mainCamera.transform.forward*near - toHeight -toRight;
bottomLeft*=scale;
Vector3 bottomRight=mainCamera.transform.forward*near -toHeight +toRight;
bottomRight*=scale;
fourPoint.SetRow(0,bottomLeft);
fourPoint.SetRow(1,bottomRight);
fourPoint.SetRow(2,topRight);
fourPoint.SetRow(3,topLeft);
fogMaterial.SetMatrix("_FourRay",fourPoint);
}
void OnRenderImage(RenderTexture src,RenderTexture dest){
if(fogMaterial!=null){
SetRay();
fogMaterial.SetVector("_lightDir",lightDir);
fogMaterial.SetColor("_fogColor",fogColor);
fogMaterial.SetFloat("_lightIntensity",LightIntensity);
fogMaterial.SetColor("_lightColor",LightColor);
fogMaterial.SetFloat("_cloudSize",cloudSize);
fogMaterial.SetFloat("_moveSpeed",moveSpeed);
fogMaterial.SetFloat("_startDistance",startDistance);
fogMaterial.SetFloat("_rayDistance",endDistance);
fogMaterial.SetInt("_stepNum",stepNum);
fogMaterial.SetFloat("_maxHeight",maxHeight);
fogMaterial.SetFloat("_densityInstence",densityInstence);
fogMaterial.SetFloat("_minCloud",cloudDenAdjust.x);
fogMaterial.SetFloat("_maxCloud",cloudDenAdjust.y);
fogMaterial.SetInt("_cloudFra",cloudFra);
fogMaterial.SetFloat("_g",g);
Graphics.Blit(src,dest,fogMaterial);
}else{
Graphics.Blit(src,dest);
}
}
}
shader1
Shader "imageEffect/volumeFog"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "VolumeFog.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 Ray:TEXCOORD1;
};
//sceneCol depthtexture
sampler2D _MainTex;
float2 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
float4x4 _FourRay;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
int index=0;
if( o.uv.x<0.5 && o.uv.y<0.5){
index = 0 ;
}else if( o.uv.x>0.5 && o.uv.y<0.5){
index = 1;
}else if( o.uv.x>0.5 && o.uv.y>0.5){
index = 2;
}else if( o.uv.x<0.5 && o.uv.y>0.5){
index = 3;
}
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
index = 3 - index;
#endif
o.Ray = _FourRay[index];
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 sceneCol=tex2D(_MainTex,i.uv);
//depth
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
float viewDepth = LinearEyeDepth(depth);
float rz = viewDepth *length(i.Ray.xyz);
//camera ro+rd
float3 ro = _WorldSpaceCameraPos;
float3 rd = normalize(i.Ray.xyz);
return ProcessRayMarch(ro,rd,rz,sceneCol);
}
ENDCG
}
}
}
shader2
#ifndef VOLUME_FOG
#define VOLUME_FOG
#define HASHSCALE1 .1031
float Hash13(float3 p3)
{
p3 = frac(p3 * HASHSCALE1);
p3 += dot(p3, p3.yzx + 19.19);
return frac((p3.x + p3.y) * p3.z);
}
float VNoise(float3 p)
{
float3 pi = floor(p);
float3 pf = p - pi;
float3 w = pf * pf * (3.0 - 2.0 * pf);
return lerp(
lerp(
lerp(Hash13(pi + float3(0, 0, 0)), Hash13(pi + float3(1, 0, 0)), w.x),
lerp(Hash13(pi + float3(0, 0, 1)), Hash13(pi + float3(1, 0, 1)), w.x),
w.z),
lerp(
lerp(Hash13(pi + float3(0, 1, 0)), Hash13(pi + float3(1, 1, 0)), w.x),
lerp(Hash13(pi + float3(0, 1, 1)), Hash13(pi + float3(1, 1, 1)), w.x),
w.z),
w.y);
}
float _moveSpeed;
float3 _lightDir;
float3 _fogColor;
float _g;
int _stepNum;
float _startDistance;
float _rayDistance;
float _densityInstence;
float _minCloud;
float _maxCloud;
int _cloudFra;
float _maxHeight;
float _cloudSize;
float3 _lightColor;
float _lightIntensity;
float FBM( float3 p,float iterNum,float _maxHeight,float _cloudSize)
{
//设置高度
float alpha = smoothstep(_maxHeight , _maxHeight-5, p.y);
//调整cloud大小
p *= _cloudSize/5;
p+= float3(1,1,3) * _Time.y * _moveSpeed;
float f = 0.0;
float s = 0.5;
float s2 = 2.00;
float sum = 0.0;
for(int i = 0;i< iterNum;i++){
f += s * VNoise( p );
p *=s2;
sum+=s;
s*= 0.5;s2+=0.01;
}
return (f/sum) * alpha;
}
//得到密度
float getDen(float3 p ){
float den = FBM(p , _cloudFra , _maxHeight,_cloudSize);
den = smoothstep(_minCloud , _maxCloud,den);
return den;
}
float4 rayMarch(float3 ro , float3 rd , float rz,float3 sceneCol){
//num
float4 light=float4(0,0,0,0);
float t = _startDistance; //开始距离
float stepSize = (_rayDistance-_startDistance)/_stepNum;
float3 p=float3(0,0,0);
for(int i =0;i<_stepNum;i++){
p = ro + t *rd;
//各种限制节省投射次数------------------------------------
if(light.a>0.99||t>=rz){
break;
}
if(rd.y>0 && p.y>_maxHeight){
break;
}
//消除层投射与场景的硬结边1
float alpha=1.0;
if(t>=rz-10){
alpha = smoothstep(0,10,rz-t);
}
//每个采样点的各种限制------------------------------------
float den = getDen(p);
if(den>0.01){
//此处的云是否被灯源方向的云遮挡
float dif = clamp((den - getDen(p-1*_lightDir))/0.6, 0.0 , 1.0 );
float3 lin = float3(0.65,0.7,0.75)*1.4 + _lightColor * dif * _lightIntensity;
//浓度做Alpha
float4 col = float4(_fogColor,den);
col.xyz*=lin;
//透光比 距离与浓度
col.xyz = lerp(col.xyz , sceneCol.xyz , exp(-t*t));
//消除层投射与场景的硬结边2
col.a *= alpha ;
//添加散射衰减
float cosTheta=dot(_lightDir,-rd);
float result = 1/(4*3.14)*(1-pow(_g,2))/pow(1+pow(_g,2)-2*_g*cosTheta,1.5);
//此层的颜色 * den * 散射 * 强度控制
col.xyz *= col.a * result * _densityInstence;
light = light + col*(1.0-light.a);
}
t+=stepSize;
}
return light;
}
float4 ProcessRayMarch(float3 ro,float3 rd, float rz,float3 sceneCol){
float4 fogmask = rayMarch(ro,rd,rz,sceneCol);
float3 final = fogmask.xyz * fogmask.w + (1-fogmask.w) * sceneCol;
return float4(final,1.0);
}
#endif