次表面散射之BTDF实现
本文主要用Translucent Shadow Maps 来实现投射效果
主要参考:GPU Gems
BRTF阅读推荐:角色渲染技术blog
1.Transucent Shadow Maps 的应用原理;
2.unity中实现的平行光投射
3.c#代码/depthShader/objShader
1.投射的实现原理
当一个物体为半透明时,在物体较薄的地方,也会有光线穿过物体,这也就是所说了BTDF部分。
为了描述投射光线的大小,我们应考虑光线穿过物体的距离。
①用render texture 在光源空间的原点绘制一个摄像机,使其记录半透明物体的在光源空间的深度;
②在绘制物体的时候,计算片元的世界坐标转换到Light空间中,求出到原点的距离。再通过裁剪等变换取出对应的Translucent Shadow Map中的深度值。将这两个值进行相减求出:光在物体中穿过的距离。
并用此值控制投射光的强度(以上不考虑光的折射)
2.unity中的实现
①render texture 中的摄像机调整
摄像机的rotator与平行光的方向一致;
确保裁剪空间完全包含要渲染的半透明物体;
将摄像机 culling mask 与目标物体的layer保持一致;
综上写个脚本方便调节
摄像机的渲染深度不适宜调整过大,会影响TSM中物体深度精准度;
为了适宜大范围多目标的半透明物体渲染的项目
(1)分区域多增加Render Texture
(2)增加TSM贴图的精度;
这里我使用unity 内置的函数将 深度信息 编码到32bit的RGBA中
//脚本中RenderTexture的声明
depthTexture=new RenderTexture(depthCamera.pixelWidth,depthCamera.pixelHeight,8,RenderTextureFormat.ARGB32);
//shader中的编码与解码 在unityCG.cginc中定义
float4 depthRGBA = EncodeFloatRGBA(distance0_1);
float d_i=DecodeFloatRGBA(distanceColor);
②shader 中的算法(具体细节解释请看GPU Gems)
(1)将算法改为平行光
因为时平行光,为了方便计算我们将near裁剪平面 设置为0;
将distance转为depth,再 /far 的值 将距离转为【0,1】;
之后就可以调用EncodeFloatRGBA(float)编码了,编码后显示的图片
当你看见从你最近的地方图像开始画圈圈,说明此步正确
(2)在绘制物体时对render texture进行采样
用脚本将lightCamera的viewMatrix和VPMatrix矩阵传给 绘物体的shader
比较绕的地方:将用世界坐标点求出 depthTexture 对应的uv
裁剪空间的齐次变换后,xy分量的范围是【-1,1】
可以通过 /2.后再+0.5
也可以 +1.后再/2. 哈哈!!
得到(0,1)之间
float4 texCoord =mul(_LightTexMatrix,i.worldPos);
float4 distanceColor=tex2D(_DistanceTex,((texCoord.xy/texCoord.w)/2)+0.5);
3.代码
C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class btdfScript : MonoBehaviour
{
public Transform objTransform;
public Transform dirLightTransform;
private Transform depthCameraTransform;
public float armLength;
private Camera depthCamera;
private RenderTexture depthTexture;
public Shader drawDepthShader;
public Material mt;
void Start()
{
depthCameraTransform= GetComponent<Transform>();
depthCamera=GetComponent<Camera>();
depthCamera.enabled=false;
//depthCamera.clearFlags=CameraClearFlags.Nothing;
depthTexture=new RenderTexture(depthCamera.pixelWidth,depthCamera.pixelHeight,8,RenderTextureFormat.ARGB32);
//depthTexture.hideFlags = HideFlags.DontSave;
}
void Update()
{
depthCameraTransform.position = objTransform.position - dirLightTransform.forward * armLength ;
depthCameraTransform.LookAt(objTransform.position);
if(drawDepthShader){
//depthCamera.CopyFrom
depthTexture.Release();
depthCamera.targetTexture=depthTexture;
depthCamera.RenderWithShader(drawDepthShader,"");
//depthTexture.apply()
}
mt.SetTexture("_DistanceTex",depthTexture);
mt.SetMatrix("_LightMatrix" , depthCamera.worldToCameraMatrix);
mt.SetMatrix("_LightTexMatrix" , depthCamera.projectionMatrix * depthCamera.worldToCameraMatrix);
mt.SetFloat("_LightFarCP",depthCamera.farClipPlane);
}
}
depthShader
Shader "Unlit/DistanceShader"
{
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Cull back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal: NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 viewPos:TEXCOORD1;
};
v2f vert (a2v v)
{
v2f o;
v.vertex.xyz += v.normal * 0.01;
o.pos = UnityObjectToClipPos(v.vertex);
o.viewPos= UnityObjectToViewPos(v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
float distance = length(i.viewPos);
float distance1 = distance *dot(normalize(-i.viewPos) , float3(0,0,1));
float distance0_1 = distance1/_ProjectionParams.z;
float4 depthRGBA = EncodeFloatRGBA(distance0_1);
//return fixed4(i.distance,i.distance,i.distance,1.0);
return depthRGBA;
}
ENDCG
}
}
}
objShader
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unlit/BRTFshader"
{
Properties
{
_MainTex("MainTex",2D)="white"{}
_DiffuseColor("DiffuseColor",Color)=(1,1,1,1)
_SpecularColor("SpecularColor",Color)=(1,1,1,1)
_Shinness("Shinness",Range(0,300))=150
_Wrap("Wrap",Range(0,1))=0.5
_ScatterFactor("ScatterFactor",Range(0,1))=0.5
_DistanceTex ("DistanceTex", 2D) = "white" {}
_ssDistanceScale("ssDistanceScale",float)=1
_ssPow("ssPow",float)=1
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include"UnityPBSLighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float4 worldPos:TEXCOORD1;
float3 worldNormal:TEXCOORD2;
};
//s
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
float _Shinness;
float _Wrap;
sampler2D _DistanceTex;
float4x4 _LightMatrix;
float4x4 _LightTexMatrix;
float _ssDistanceScale;
float _ssPow;
float _LightFarCP;
float _ScatterFactor;
v2f vert (appdata v)
{
v2f o;
o.pos= UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldPos=mul(unity_ObjectToWorld,v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
float trace(v2f i){
float4 texCoord =mul(_LightTexMatrix,i.worldPos);
float4 distanceColor=tex2D(_DistanceTex,((texCoord.xy/texCoord.w)/2)+0.5);
float d_i=DecodeFloatRGBA(distanceColor);
d_i = d_i *_LightFarCP;
float3 InLightPos=mul(_LightMatrix,i.worldPos).xyz;
float d_o = distance(InLightPos , float3(0,0,0));
d_o = d_o * dot(normalize(-InLightPos) , float3(0,0,1));
return d_o-d_i;
}
fixed4 frag (v2f i) : SV_Target
{
float traceDistance=trace(i);
// sample the texture
fixed3 scattering = pow(exp(-traceDistance*_ssDistanceScale) ,_ssPow) * _LightColor0.xyz ;
//data
float3 worldNormal=normalize(i.worldNormal);
float3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
float3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
//albedo
fixed3 albedo = tex2D(_MainTex, i.uv).xyz * _DiffuseColor.xyz;
//ambient
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//specular
float3 halfDir=normalize(worldLightDir+worldViewDir);
//wrap
float wrap=(dot(worldLightDir,worldNormal) + _Wrap) / (1 + _Wrap);
wrap = max(0,wrap);
float wrapDiffuse=_LightColor0.xyz * wrap * albedo;
//specualr
float3 specualr = _LightColor0.xyz * _SpecularColor.xyz * pow(max(0,dot(worldNormal,worldLightDir)),_Shinness);
fixed3 color= lerp(ambient + wrapDiffuse , scattering , _ScatterFactor) + specualr;
return fixed4(color,1.0);
}
ENDCG
}
}
}