shader入门精要读书笔记37 全局雾效(重建世界坐标另一个方法)

一、前言

Untiy内置的雾效可以产生基于距离的线性或指数雾效。

而如果我们要在自己的Shader中实现雾效,我们可能会使用到 #pragma multi_compile_fog ,UNITY_FOG_COORDS,UNITY_TRANSFER_FOG,UNITY_APPLY_FOG等等 内置宏。

基于屏幕后处理的全局雾效的实现:
这种方法我们不需要更改场景内渲染物体的Shader代码,仅仅使用一次屏幕后处理效果即可。
这种方法自由度高,我们可以模拟出各种雾效,均匀、基于距离的线性/指数、基于高度的雾效等等。

二、具体实现

这种屏幕后处理全局雾效果的关键在于 我们需要使用深度纹理中的深度值重建世界空间下的像素位置。

之前我们已经实现了重建世界坐标的方法:深度纹理 重建世界坐标实现像素速度存储—运动模糊
上面这个是在片元着色器中进行了复杂的矩阵运算,很影响性能。

更加好的重建世界坐标的办法是:
对图像空间下的视锥体射线(从摄像机出发,指向图像上某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息。然后我们再把射线和线性空间下的深度值相乘,再加上摄像机的世界位置,就可以得到该像素的世界空间位置了,

得到世界空间像素位置后,我们再使用各种公式,模拟全局雾效。

世界坐标重建具体见 P276-P278.

大概过程为:

通过摄像机世界空间位置加上偏移计算出像素世界空间位置

偏移计算通过 深度纹理计算得到的线性深度值 乘以 顶点着色器输出并插值后的射线(同时包含方向和距离),

上面插值的意思是 对摄像机近裁剪平面四个角的某个特定向量的插值。

然后就是计算这四个特定向量,使用摄像机相关系数和方向与矢量加减运算(P277)。

得到四个特定向量后,我们不能使用深度值直接与四个角的单位方向乘积进行计算偏移量,因为我们得到的线性深度值并非是点到摄像机的欧氏距离,而是z方向上的距离

所以我们要把深度值转换成到摄像机的欧氏距离。
然后就不懂了,图13.7这里的像素深度值到底是在哪个平面???
琢磨两天了。???查一些资料也不懂啊。

只能确定射线是通过系数乘以方向,系数是通过相似三角形计算的.。

找到一个知乎回答大概明白什么意思了。

—链接: 上面这个问题

首先构造在世界空间中从摄像机指向屏幕像素点的向量。

o.worldSpaceDir = WorldSpaceViewDir(v.vertex);

将向量转换到观察空间,存储其z分量的值。注意向量和位置的空间转换是不同的,当w分量为0的时候Unity会将其视为向量,而当w分量为1的时候Unity将其视为位置。

o.viewSpaceZ = mul(UNITY_MATRIX_V, float4(o.worldSpaceDir,
0.0)).z;

在深度缓冲中采样。这里使用tex2Dproj而不是tex2D的原因是screenPos是用ComputeScreenPos来计算得到的,用tex2Dproj可以帮我们做透视除法。

float eyeDepth =
UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, i.screenPos));
eyeDepth = LinearEyeDepth(eyeDepth);

因为像素点的观察线性深度就是其在观察空间中的z分量,所以根据向量的z分量计算其缩放因子,将向量缩放到实际的长度。

i.worldSpaceDir *= -eyeDepth / i.viewSpaceZ;
最后以摄像机为起点,缩放后的向量为指向向量,得到像素点在世界空间中位置。

float3 worldPos = _WorldSpaceCameraPos + i.worldSpaceDir;

这里把问题描述成了:求观察空间下 视角方向的z轴、像素观察方向的线性深度,然后我们就可以求出缩放因子。

我看了一下i.worldSpaceDir *= -eyeDepth / i.viewSpaceZ;其实就是上面书里求的比例,再乘以方向和linearDepth。

只不过这个使用的视角方向,书中使用的是近裁剪平面4个角,

书中解释4个角的原因是 对应近裁剪平面4个角计算出来后,传入顶点着色器,顶点着色器会根据当前的位置选择对应向量,插值后传到interpolatedRay。

希望有个大佬跳出来给我解释下 -o-

三、雾效计算

//雾效系数f,原始颜色和雾效颜色
float3 afterFog = f*fogColor+(1-f)*origColor;
//线性雾效系数f的计算
f = ( H(end)-y ) / (H(end)-H(start))

雾效其他 f 计算方法(线性、指数、指数平方见P278)

四、具体实现

摄像机代码:

using UnityEngine;
using System.Collections;

public class FogWithDepthTexture : 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.0f, 3.0f)]
	public float fogDensity = 1.0f;		//雾的浓度

	public Color fogColor = Color.white;		//雾的颜色

	public float fogStart = 0.0f;		//起始高度
	public float fogEnd = 2.0f;		//终止高度

	void OnEnable() {
		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;			//计算系数
			
			//这里开始计算4个向量
			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;

			frustumCorners.SetRow(0, bottomLeft);
			frustumCorners.SetRow(1, bottomRight);
			frustumCorners.SetRow(2, topRight);
			frustumCorners.SetRow(3, topLeft);		//存到每一行

			material.SetMatrix("_FrustumCornersRay", frustumCorners);		//向Shader中传递矩阵

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

			Graphics.Blit (src, dest, material);		//进行渲染  
		} else {
			Graphics.Blit(src, dest);
		}
	}
}

Shader代码:

Shader "Unity Shaders Book/Chapter 13/Fog With Depth Texture" {
	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
	}
	SubShader {
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		float4x4 _FrustumCornersRay;
		
		sampler2D _MainTex;
		half4 _MainTex_TexelSize;
		sampler2D _CameraDepthTexture;
		half _FogDensity;
		fixed4 _FogColor;
		float _FogStart;
		float _FogEnd;
		
		struct v2f {
			float4 pos : SV_POSITION;
			half2 uv : TEXCOORD0;
			half2 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];		//通过索引值获取对应行的interpolatedRay
				 	 
			return o;
		}
		
		fixed4 frag(v2f i) : SV_Target {
			float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));		//获取线性深度值
			float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;		//得到世界空间的坐标
						
			float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);			//用于模拟从下到上渐渐稀薄的系数
			fogDensity = saturate(fogDensity * _FogDensity);			//总的雾浓度
			
			fixed4 finalColor = tex2D(_MainTex, i.uv);		//采样主纹理
			finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);		//在雾的颜色和源颜色插值
			
			return finalColor;
		}
		
		ENDCG
		
		Pass {
			ZTest Always Cull Off ZWrite Off
			     	
			CGPROGRAM  
			
			#pragma vertex vert  
			#pragma fragment frag  
			  
			ENDCG  
		}
	} 
	FallBack Off
}

完事儿

现在就是那个scale原理最后不懂,

正交摄像机对以上不适配。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值