Unity Shader - “快速“ 次散射 (Fast SSS : Fast Subsurface Scattering)

141 篇文章 34 订阅


环境

unity : 2018.2.11f1, 2020.3.18f1
pipeline : BRP, URP


目的

备份,备忘


思路

一般次散射效果都是使用 光线追踪 来渲染的话,还原度会很高(也不是100精准,都是模拟,毕竟物质内的如何让光子偏移方向散射出来,这不是现代计算机,现代渲染架构能支撑得起的)

所以这个 假 次散射,相对 实时渲染效果的价值还是有的

具体思路:

  • 导出模型的厚度图
  • 将片段(像素)中的 观察位置方向 和 光源入射方向 做 dot
  • 然后控制 dot 后的 pow, scale 来控制边缘,和 整体亮度
  • 叠加到之前的颜色着色上即可

在这里插入图片描述
在这里插入图片描述


次表面散射原理

次表面散射我就 简称为:次散射

  • 次散射:SubSurface Scattering,简写:SSS
  • 次散射不是透明(两者没有什么关系,次散射是透光的,只不过吸收,直射,物质内部漫反射比较特殊)
  • 次散射不是透射(准确的说是,还是透射的,没透射光线,你怎么看到光照效果?只不过先经过了介质内部的 漫反射,吸收,折射后,光波 波长,波频都可能发生了变化)
  • 次散射可以使用光线追踪实现,但是比较计算量比较大
  • 实时游戏中更多的是对次散射“快速模拟”

在这里插入图片描述

快速的模拟当中,可以简单概括:SSS 包含:背光、扰动、扩散


背光

根据我们的 lambert 可以得到正面光照的 diffuse 效果 lambert = dot(L, N);,这是基于 表面(N)受正面光照的强度

那么要相对于我们的眼睛,透过物体的某个片段是,判断该片段是否在视角(V)、光源是背光的 back4L = dot(-L, V) 即可


背光强度扰动

在这里插入图片描述

背光强度 = dot(V, -normalize(L + a * N) = dot(V, -H),其中 0 <= a <= 1 可以控制强度

如果为了性能考虑,可以省去:normalize,反正都是快速模拟,不用太精准

所以我们可以将公式修改为:背光强度 = dot(V, -(L+a * N) = dot(V, -H)


背光扩散

在扰动公式基础上调整为:背光强度 = (saturate(dot(V, -(L + a * N))^p) * s

现将 背光强度 clamp[0~1] 之间,所以使用 saturate,然后再用 pow(x, p)p 来控制边缘大小

因此:其中 0 < p <= +R 是控制扩展边缘大小,0 <= s <= +R 是控制背光整体强度值


Translucent shader 伪代码示例

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

...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
uniform float _TIIvAttenByDistance; // 1.0 / atten
...

fixed4 frag(...) : SV_Target
{
	// jave.lin : 原始光照颜色
	fixed4 albedo = tex2D(_MainTex, i.uv);
	fixed3 diffuse = lamert;
	fixed3 specular = blinn phong mode...;
	fixed3 ambient = ...;
	fixed4 finalColor;
	finalColor.rgb = lambert + blinn phong + ambient + ...;
	finalColor.a = 1;
	
	// jave.lin : Shadow map
	float shadowMapDepth = tex2D(_ShadowMap, i.shadowProjUV);
	float3 shadowMapDepth_positionShadowWS = ...;
	float3 shadowMapDepth_positionMainCamWS = ...;
	float distance_with_shadow = distance(i.positionWS.xyz - shadowMapDepth_positionMainCamWS);
	
	// jave.lin : 计算透光度
	float3 L = normalize(_WorldSpaceLightPos.xyz);
	float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
	float3 N = s.Normal;
	float3 TH = normalize(L + N * _TIDistortion);
	float translucentAtten = distance_with_shadow * _TIIvAttenByDistance;
	float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
	translucentIntensity *= translucentAtten;
		
	// jave.lin : 叠加透光颜色
	finalColor.rgb += albedo.rgb * _LightColor.rgb * translucentIntensity;
	
	return finalColor;
}

添加采样厚度图 伪代码

在上面的代码基础上,添加一个图

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

...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
...

fixed4 frag(...) : SV_Target
{
	// jave.lin : 原始光照颜色
	fixed4 albedo = tex2D(_MainTex, i.uv);
	fixed3 diffuse = lamert;
	fixed3 specular = blinn phong mode...;
	fixed3 ambient = ...;
	fixed4 finalColor;
	finalColor.rgb = lambert + blinn phong + ambient + ...;
	finalColor.a = 1;
	
	// jave.lin : 厚度采样
	float thickness = tex2D(_TITicknessMap, i.uv).r; // 0~1 => TI弱~TI强
	
	// jave.lin : 计算透光度
	float3 L = normalize(_WorldSpaceLightPos.xyz);
	float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
	float3 N = s.Normal;
	float3 TH = normalize(L + N * _TIDistortion);
	float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
	translucentIntensity *= thickness;	
		
	// jave.lin : 叠加透光颜色
	finalColor.rgb += albedo.rgb * _LightColor.rgb * translucentIntensity;
	
	return finalColor;
}

考虑上 shadow map 距离的 伪代码

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

...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
uniform float _TIIvAttenByDistance; // 1.0 / atten
...

fixed4 frag(...) : SV_Target
{
	// jave.lin : 原始光照颜色
	fixed4 albedo = tex2D(_MainTex, i.uv);
	fixed3 diffuse = lamert;
	fixed3 specular = blinn phong mode...;
	fixed3 ambient = ...;
	fixed4 finalColor;
	finalColor.rgb = lambert + blinn phong + ambient + ...;
	finalColor.a = 1;
	
	// jave.lin : Shadow map
	float shadowMapDepth = tex2D(_ShadowMap, i.shadowProjUV);
	float3 shadowMapDepth_positionShadowWS = ...;
	float3 shadowMapDepth_positionMainCamWS = ...;
	float distance_with_shadow = distance(i.positionWS.xyz - shadowMapDepth_positionMainCamWS);
	
	// jave.lin : 厚度采样
	float thickness = tex2D(_TITicknessMap, i.uv).r; // 0~1 => TI弱~TI强
	
	// jave.lin : 计算透光度
	float3 L = normalize(_WorldSpaceLightPos.xyz);
	float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
	float3 N = s.Normal;
	float3 TH = normalize(L + N * _TIDistortion);
	float translucentAtten = distance_with_shadow * _TIIvAttenByDistance;
	float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
	translucentIntensity *= translucentAtten * thickness;	
		
	// jave.lin : 叠加透光颜色
	finalColor.rgb += albedo.rgb * _LightColor.rgb * translucentIntensity;
	
	return finalColor;
}

多光源下的 SSS

下面是以类似 URP 中的多光源遍历的方式 (BRP 中是区分 Pass 来绘制多光源的)

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

...
uniform float _TIDistortion;
uniform float _TIPower;
uniform float _TIScale;
uniform float _TIIvAttenByDistance; // 1.0 / atten
uniform float _TIAmbientScale;
...

fixed4 frag(...) : SV_Target
{
	fixed4 finalColor = 0;
	float3 V = normalize(_WorldSpaceCameraPos.xyz - i.positionWS.xyz);
	float3 N = normalize(i.normalWS);
	fixed3 ambient = ...;
	
	fixed4 albedo = tex2D(_MainTex, i.uv);
	// jave.lin : 厚度采样
	float thickness = tex2D(_TITicknessMap, i.uv).r; // 0~1 => TI弱~TI强

	for (int i = 0; i < _AvailidatedLightCount; ++i)
	{
		Light light = Lights[i];

		// jave.lin : 光源方向
		float3 L = light.dir;
	
		// jave.lin : 原始光照颜色
		fixed3 diffuse = lamert with N and L;
		fixed3 specular = blinn phong mode with V, N, L;
		finalColor.rgb = lambert + blinn phong + ...;
		
		// jave.lin : Shadow map - 这部分多光源的话,不同类型的 shadow map 是不同的
		// 点光源可能是:一个 cube map,或是 6 张 shadow map
		// spot, directional light 一张
		// 这里不光是
		float3 shadowSampleLocation = GetShadowMapSampleLocation(light);
		float shadowMapDepth = tex2D(GetShadowMap(light), i.shadowProjUV);
		float3 shadowMapDepth_positionShadowWS = ...;
		float3 shadowMapDepth_positionMainCamWS = ...;
		float distance_with_shadow = distance(i.positionWS.xyz - shadowMapDepth_positionMainCamWS);
		
		// jave.lin : 计算透光度
		float3 TH = normalize(L + N * _TIDistortion);
		float translucentAtten = distance_with_shadow * _TIIvAttenByDistance;
		float translucentIntensity = pow(saturate(dot(V, -TH)), _TIPower) * _TIScale;
		translucentIntensity *= translucentAtten * thickness;	
			
		// jave.lin : 叠加透光颜色
		finalColor.rgb += albedo.rgb * light.color.rgb * translucentIntensity;
	}
	// jave.lin : 最后添加上 ambient 的计算
	finalColor.rgb += albedo.rgb * ambient * (_TIAmbientScale * thickness);

	finalColor.a = 1;
	
	return finalColor;
}

BRP 多光源效果

我们将 厚度值 放到 MRAT 贴图中的 a 通道
在这里插入图片描述

厚度值的显示
在这里插入图片描述

下面是 BRP 中的效果
请添加图片描述
请添加图片描述

请添加图片描述


URP 多光源效果

(URP下麻烦很,毕竟是个 package,需要修改其里面的 shader,具体可以参考另一篇: Unity - 如何修改一个 Package 或是如何将 Package Local化

没开效果
请添加图片描述
开了效果
请添加图片描述

关了
请添加图片描述

开了
请添加图片描述


SP 中贴图导出设置

在这里插入图片描述


(后续找个 皮肤的 素材来看效果)


用途

可以用于一些 散射透光强 的材质:

  • 玉石
  • 皮肤
  • 浓稠不透明的液体
  • 橡胶
  • 等等。。。

Project


其他方式 - RampTexture Or LUT

后面重写编写的:Unity Shader - SSS皮肤


References

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: #提问者没有完整的问题,无法给出全面的回答,请完善问题# 如果您要问的是如何使用Python和Unity进行Socket通信,那么我可以为您提供一些信息。Socket通信是指通过网络连接在不同的计算机上进行数据传输的过程。Python和Unity可以通过Socket协议实现实时的数据传输和通信。 对于Python和Unity之间的Socket通信,Python负责服务器端,Unity负责客户端。Python服务器将数据发送到Unity客户端,Unity客户端接收并解析数据。最常用的Socket协议是TCP(Transmission Control Protocol)协议,它提供了单一的连接,并将数据包使用特殊的数据结构进行编码和解码。 在开始使用Python和Unity进行Socket通信之前,需要安装Python的Socket模块和Unity的网络功能模块。然后,需要编写Python服务器和Unity客户端的代码,实现数据的发送和接收。 Python Socket模块是Python用于网络编程的核心模块。它提供了内置的套接字对象,可用于创建和操作Socket。在此基础上,可以编写一个Python服务器端程序,并绑定到IP地址和端口上。 Unity客户端使用C#,其自带的网络功能模块可以用于发送和接收数据。在Unity中,需要创建网络Socket连接,并进行连接、发送和接收操作。 总的来说,Python和Unity之间的Socket通信需要使用TCP协议,并需要编写Python服务器和Unity客户端的代码,才能实现数据的实时传输和通信。在实际工程中,还需要考虑数据格式、安全性等问题。 ### 回答2: #Python-Unity-Socket通信:Unity (C#)的实现 Python-Unity-Socket通信可以让Python代码与Unity (C#)代码实现良好的互动。通过Socket通信,Python和Unity都可以发送和接收网络数据,实现各种场景和功能。 实现Python-Unity Socket通信的步骤如下: 1. 在Python中建立Socket服务器(Server),等待Unity连接 2. 在Unity中建立Socket客户端(Client),连接到Python Socket服务器 3. Python和Unity互相发送和接收数据 在Python中,建立Socket服务器的代码如下: ``` import socket host = '127.0.0.1' port = 8888 s = socket.socket() s.bind((host, port)) s.listen(1) conn, addr = s.accept() print("连接地址:" + str(addr)) while True: data = conn.recv(1024).decode() if not data: break print("接收数据:" + data) conn.sendall(data.encode()) conn.close() ``` 在Unity中,建立Socket客户端的代码如下: ``` using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Net.Sockets; using System.Net; using System; public class SocketClient : MonoBehaviour { public string host = "127.0.0.1"; public int port = 8888; private TcpClient client; // Use this for initialization void Start () { try { client = new TcpClient(); client.Connect(host, port); } catch (Exception e) { Debug.Log(e); } string message = "Hello Python"; byte[] data = System.Text.Encoding.UTF8.GetBytes(message); NetworkStream stream = client.GetStream(); stream.Write(data, 0, data.Length); data = new byte[1024]; string responseData = string.Empty; int bytes = stream.Read(data, 0, data.Length); responseData = System.Text.Encoding.UTF8.GetString(data, 0, bytes); Debug.Log("收到服务器消息:" + responseData); stream.Close(); client.Close(); } } ``` 以上代码实现了Unity向Python发送一条消息,并接收Python回传的消息。 Python和Unity之间还可以通过Socket发送和接收其他类型的数据,如音频、视频等。需要注意的是,Python和Unity发送和接收数据的格式需要保持一致,可以使用Json、Protobuf等数据格式来统一。 ### 回答3: # 应该题目描述不太准确,这里按照理解给出回答 python-unity-socket-communication,是指使用Python语言和Unity游戏引擎之间进行网络Socket通信。 具体实现中,需要在Unity中编写C#代码,通过Socket连接Python服务器,实现网络通信。 在Unity中,首先需要使用Socket创建一个客户端连接到Python服务器,并通过该连接向Python服务器发送请求和接收响应。同时,需要编写代码来解析Python服务器返回的数据,以便Unity游戏引擎正确地使用它们。 在Python服务器中,需要编写Socket服务器程序,以便监听来自Unity客户端的请求,并对其做出响应。在响应Unity客户端请求时,需要将Python语言中的数据格式转换为Unity游戏引擎所需的格式。 总之,Python-Unity-Socket通信是一种常用且强大的网络通信方式,可以帮助Unity游戏开发人员高效地与Python服务器进行通信,以实现更为复杂的游戏功能和应用场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值