在正常情況下,Diffuse在背光面都是純黑的,爲了模擬簡單的次表面散射,引入環繞光照,環繞光照對Diffuse做出如下修改。
float diffuse = max(0,dot(L,N));
float wrap_diffuse = max(0,(dot(L,N)+_Wrap)/(1+_Wrap));//_Wrap爲外部傳入參數
下圖是環繞光照函數的圖表:
在進行了對環繞光照處理的漫反射模擬的次表面反射對真實性還遠遠不夠,所以我們要在上述基礎上用深度映射模擬吸收。
吸收是模擬半透明材質最重要的因素之一。光線在物質中傳播的越遠,被散射和吸收的就越厲害,爲了模擬這種效果,我們需要測量光在物質中傳播的距離,估算這個距離的方法就是深度映射。深度映射的實現原理即我們要在光源的視點去渲染場景,存儲光源到某個模型的表面距離到RTT中。然後在渲染玉石的shader中得到這個深度,再計算當前渲染點到光源的距離,兩者的差值就是光傳播的距離,得到光在物質中的傳播距離後可以索引美工所創建的一維紋理,或者可以選擇直接計算指數函數 。
return exp(-Dist*sigma_t);
注意:此種方案僅適用於模型處於光源和視角之間的時候,即視角在背光面。使用深度映射計算光在物體中傳播的距離的示意圖如下:
具體實現代碼如下:
這個shader是在光源空間對物體表面深度存儲
Shader "Custom/DepthShader"
{
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float2 depth:TEXCOORD0;
float4 pos : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.depth.xy =o.pos.zw ;
return o;
}
float4 frag (v2f i) : SV_Target
{
float d = i.depth.x/i.depth.y;
return Linear01Depth(d);
}
ENDCG
}
}
}
配合上述代碼的cs腳本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DepthTest : MonoBehaviour
{
public Transform lightD;
public Material mt;
public Shader depthShader;
public RenderTexture m_depthTexture;
private Camera camereDepth;
void Start ()
{
m_depthTexture = new RenderTexture ((int)Camera.main.pixelWidth, (int)Camera.main.pixelHeight, 24);
m_depthTexture.hideFlags = HideFlags.DontSave;
GameObject go = new GameObject ("camereDepth");
go.transform.position = lightD.position;
go.transform.rotation = lightD.rotation;
camereDepth = go.AddComponent<Camera> ();
camereDepth.farClipPlane = 2;//此種方案的缺陷,精度不夠,必須把遠裁剪面調小
camereDepth.enabled = false;
//camereDepth.CopyFrom (Camera.main);
Matrix4x4 lightTexMatrix = camereDepth.projectionMatrix * camereDepth.worldToCameraMatrix * Matrix4x4.identity;
mt.SetFloat ("_CamNearPlane", camereDepth.nearClipPlane);
mt.SetFloat ("_CamFarPlane", camereDepth.farClipPlane);
mt.SetMatrix ("_WolrdtoLightMatrix", camereDepth.worldToCameraMatrix);
mt.SetMatrix ("_LightTexMatrix", lightTexMatrix);
}
// Update is called once per frame
void OnPreRender ()
{
if (null != depthShader) {
camereDepth.targetTexture = m_depthTexture;
camereDepth.RenderWithShader (depthShader, "");
mt.SetTexture ("_BackDepthTex", m_depthTexture);
}
}
}
最後物體渲染的shader如下:
Shader "Custom/wrap_diff1"
{
Properties {
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
_Specular("Specular",Color)=(1.0,1.0,1.0,1.0)
_Shinness("Shinness",Range(8,256))=128
_Wrap("Wrap",Range(0,1))=0.5
_ScatterWidth("_ScatterWidth",Vector)=(0,0,0,0)
_ScatterFactor("_ScatterFactor",Range(0,1))=0.75
_MainTex("MainTex",2D)="white"{}
_ScatterTex("_ScatterTex",2D)="white"{}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "Lighting.cginc"
fixed4 _Diffuse;
float _Wrap;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _ScatterWidth;
float _ScatterFactor;
sampler2D _ScatterTex;
float4 _ScatterTex_ST;
float4 _Specular;
float _Shinness;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent:TANGENT;
float2 texcoord:TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 wNormal : TEXCOORD0;
float4 wPos:TEXCOORD1;
float4 uv:TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.wNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.wPos = mul(unity_ObjectToWorld,v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_ScatterTex);
return o;
}
sampler2D _BackDepthTex;
float4x4 _WolrdtoLightMatrix;
float4x4 _LightTexMatrix;
float _CamNearPlane;
float _CamFarPlane;
fixed4 frag(v2f i) : SV_Target {
float d_o =mul(_WolrdtoLightMatrix,i.wPos).z;
d_o = (-d_o-_CamNearPlane)/(_CamFarPlane-_CamNearPlane);
float4 tpos = mul(_LightTexMatrix,i.wPos);
float4 scrPos = tpos * 0.5f;
scrPos.xy = float2(scrPos.x, scrPos.y*1) + scrPos.w;
scrPos.zw = tpos.zw;
float4 backDepthColor = SAMPLE_DEPTH_TEXTURE(_BackDepthTex,UNITY_PROJ_COORD(scrPos));
float d_i = -(backDepthColor*(_CamFarPlane-_CamNearPlane)+_CamNearPlane);
d_i = backDepthColor.r;
float depth =d_o- d_i;
float3 scattering = exp(-_ScatterWidth.xyz*depth);
float3 N =normalize(i.wNormal);
float4 texcol = tex2D(_MainTex, i.uv.xy);
fixed3 albedo = texcol.rgb*_Diffuse.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
float3 L = normalize(UnityWorldSpaceLightDir(i.wPos));
float3 V= normalize(UnityWorldSpaceViewDir(i.wPos));
float3 H = normalize(L+V);
float wrap = (dot(L,N)+_Wrap)/(1+_Wrap);
float wrap_diff = max(0,wrap);
fixed3 diffuse = _LightColor0.rgb * wrap_diff*albedo;
float s = pow(max(0,dot(N,H)),_Shinness);
float3 specular = _LightColor0.rgb *_Specular.rgb*s;
float4 finCol = float4(0,0,0,0);
finCol.rgb =lerp(ambient+diffuse,scattering,_ScatterFactor)+specular;
finCol.a = texcol.a;
return finCol;
}
ENDCG
}
}
// FallBack "Diffuse"
}
至此,我們就可以看到如下效果
下次在分享另一種方案的模擬效果
主要參看文章如下:
https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch16.html