首页 > 其他分享 >Unity3D教程:次表面散射的简单实现

Unity3D教程:次表面散射的简单实现

时间:2024-10-23 18:10:23浏览次数:7  
标签:Unity3D 教程 lightDir float 散射 float4 col float3

次表面散射指的是光线射入半透明材质,在内部发生散射后再透射出来的光线传播过程,考虑到有些项目会需要使用次表面散射,下面就给大家介绍下在Unity3D中次表面散射的简单实现,希望可以帮到大家。

 

一、前言

本文旨在与大家一起探讨学习新知识,如有疏漏或者谬误,请大家不吝指出。

以下内容参考了GPU精粹1中关于次表面散射一节的内容。

 

二、概述

次表面散射,英文全称为Subsurface Scattering,简称SSS。指的是光线射入半透明材质,在内部发生散射后再透射出来的光线传播过程。真实世界中拥有次表面散射的材质有蜡烛、大理石、玉石以及人的皮肤等。要模拟物理上真实的次表面散射是很复杂的一件事,比较经典的次表面散射模型有BSSRDF,全称叫双向次表面散射反射分布函数。本文中并未使用BSSRDF模型,而是简单的使用了exp指数函数对深度进行计算以此模拟散射。

以下为实现后的最终效果:

 

 

三、原理

首先我们来对次表面散射物体的光照做一个分解,也算是一个简单的建模过程。

Color = Diffuse * Scattering + SpecularColor;

Diffuse指的是物体表面的漫反射颜色,Scattering是散射的颜色,SpecularColor是高光颜色。

1、漫反射的计算

漫反射的计算公式,在这里我们使用的是环绕光照计算公式,即:

 

?
1 diff = ( dot(normal, lightDir) + wrap )/( 1+wrap )

     其中normal为顶点的法线,lightDir是光照方向,wrap为环绕参数。传统的Lambert光照中,当物体表面的法线与光源方向垂直的时候,其产生的光照结果为0,但是在次表面散射物体中,由于内部散射光线的传播,导致其在上述情形下光照结果不会完全为0。所以为了减少传统Lambert光照中的黑暗区域,我们使用环绕光照公式,当dot(normal, lightDir)结果为0的时候,我们强制其至少有一点点亮度,即wrap/(1+wrap)。

2、散射的计算

我们假设物体表面的散射是均匀分布的,并且无视光源位置以及光照方向对散射的影响,取物体在视线方向上的深度值作为参数,带入exp指数函数中进行计算。当然上述假设并不符合物理规律,但是考虑到效果以及效率的问题,我们只好先这么干了。

为了获取物体在视线方向上的深度值,我们需要先以cull front模式渲染一遍物体,保存物体背面的顶点的深度值信息,然后再回到正常的cull back模式下渲染物体,使用(backDepth-frontDepth)来求出深度值,最后带入公式exp(-C*depth)中。C为外部传入的参数,用于调节物体的透光率。

3、高光的计算

高光的计算我们使用经典的BlinnPhong光照公式,即:

           

?
1 2 Specular = pow((dot(normal, half)), shiness); Half = normalize(lightDir + viewDir);

    其中normal为顶点法线向量,half为半角向量,是入射光向量与视点向量的角平分线向量,shiness为高光指数。

      BlinnPhong光照模型相比较于Phong光照模型,其高光区域更平滑柔和,这也是为什么我们使用它。

4、半透明

     由于在散射计算中,需要使用到物体表面顶点的深度值信息,导致我们在渲染时不 能关闭ZWrite,这就使得我们不能通过Unity3D中设置RenderType=Transparent、Queue=Transparent来实现半透明混合效果。在Unity3D中,要实现半透明,一般的做法是:

?
1 2 3 Tags { "RenderType"="Transparent" "Queue"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha ZWrite off

所以,我们需要寻找另外一种方法来实现半透明,通过GrabPass操作来获取除物体本身以外的屏幕渲染结果,然后我们在片段着色器中手动进行混合计算,以此达到半透明效果。当然需要注意的是,GrabPass本身的操作比Alpha混合要昂贵的多,需要牺牲更多的计算性能,另外GrabPass在某些手机平台上可能不被支持。

 

四、实现

下面给出顶点着色器以及片段着色器的实现代码:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 struct appdata {        float4 vertex : POSITION;        float2 uv : TEXCOORD0;        float3 normal:NORMAL; };    struct v2f {        float2 uv : TEXCOORD0;        float4 screenUV:TEXCOORD1;        float3 lightDir:TEXCOORD2;        float3 viewDir:TEXCOORD3;        float3 normal:TEXCOORD4;        float4 grabUV:TEXCOORD5;        UNITY_FOG_COORDS(1)        float4 vertex : SV_POSITION; };    sampler2D _MainTex; float4 _MainTex_ST; sampler2D _GrabTexture; sampler2D _BackDepthTex; float4 _AttenuationC; float4 _Color; float _Shininess; float _ScatteringFactor; float _Wrap;    struct LightingInput {        float3 Albedo;        float3 Normal;        float Gloss;        float Specular;        float Alpha; };    float4 CalculateLighting (LightingInput i, float3 lightDir, float3 viewDir, float atten, float3 scattering) {        float3 h = normalize (lightDir + viewDir);                float diff = (dot (i.Normal, lightDir)+_Wrap)/(1+_Wrap);        diff = saturate (diff);                float nh = (dot (i.Normal, h)+_Wrap)/(1+_Wrap);        nh = saturate(nh);        float spec = pow (nh, i.Specular*128.0) * i.Gloss;                float4 c;        c.rgb = (i.Albedo * _LightColor0.rgb * diff *scattering + _LightColor0.rgb * _SpecColor.rgb * spec) * (atten * 2);        c.a = i.Alpha + _SpecColor.a * spec * atten;          return c; }    v2f vert (appdata v) {        v2f o;        o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);        o.uv = TRANSFORM_TEX(v.uv, _MainTex);        o.screenUV = ComputeScreenPos(o.vertex);        o.lightDir = ObjSpaceLightDir(v.vertex);        o.viewDir = ObjSpaceViewDir(v.vertex);        o.normal = v.normal;        o.grabUV = ComputeGrabScreenPos(o.vertex);        UNITY_TRANSFER_FOG(o,o.vertex);        return o; }    float4 frag (v2f i) : SV_Target {        // sample the texture        float4 col = tex2D(_MainTex, i.uv);        //        float frontDepth = LinearEyeDepth( i.screenUV.z/i.screenUV.w );        //        float2 backDepthUV = i.screenUV.xy/i.screenUV.w;        float4 backDepthColor = tex2D(_BackDepthTex, backDepthUV);        float backDepth = LinearEyeDepth(DecodeFloatRGBA(backDepthColor));        //do scattering        float depth = backDepth-frontDepth;        float3 scattering = exp(-_AttenuationC.xyz*depth);        //do lighting        LightingInput lightVar;        lightVar.Albedo = col.rgb * _Color.rgb;        lightVar.Gloss = col.a;        lightVar.Alpha = col.a * _Color.a;        lightVar.Specular = _Shininess;        lightVar.Normal = i.normal;          col = CalculateLighting (lightVar, i.lightDir, i.viewDir, _LightColor0.a, scattering);        //blend        //col.xyz = col.a*col.rgb + (1-col.a)*tex2D(_GrabTexture, i.grabUV.xy/i.grabUV.w);        col.xyz = lerp(tex2D(_GrabTexture, i.grabUV.xy/i.grabUV.w), col.rgb, col.a);        // apply fog        UNITY_APPLY_FOG(i.fogCoord, col);        return col; }

 

照例,我们对上述代码中某些函数进行说明:

1、ComputeScreenPos函数是将经过透视投影的顶点变换到屏幕坐标系中,然后就可以使用xy/w的值作为UV取屏幕坐标系下的深度图的值。

具体细节可以参看UnityCG.cginc文件,这里也将代码贴出来:

?
1 2 3 4 5 6 7 8 9 10 11 inline float4 ComputeScreenPos (float4 pos) {        float4 o = pos * 0.5f;        #if defined(UNITY_HALF_TEXEL_OFFSET)        o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w * _ScreenParams.zw;        #else        o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;        #endif                o.zw = pos.zw;        return o; }

 

2、ComputeGrabScreenPos函数做的事情跟上述ComputeScreenPos函数是一样的,只不过对于GrabPass取到的渲染结果与屏幕空间不太一致,这里也列出代码:

?
1 2 3 4 5 6 7 8 9 10 11 inline float4 ComputeGrabScreenPos (float4 pos) {        #if UNITY_UV_STARTS_AT_TOP        float scale = -1.0;        #else        float scale = 1.0;        #endif        float4 o = pos * 0.5f;        o.xy = float2(o.x, o.y*scale) + o.w;        o.zw = pos.zw;        return o; }

 

3、LinearEyeDepth函数是将经过透视投影变换的深度值还原成其在View坐标系中的值。具体细节读者可以参考此链接:

http://blog.sina.com.cn/s/blog_70f96aa90102v0wd.html

这篇博客是本人早期写的,大体意思有说明到,如果要穷究细节,则需要对透视投影做深入了解了。

4、DecodeFloatRGBA与EncodeFloatRGBA是一对函数。EncodeFloatRGBA用于将float值编码到RGBA四个通道上,Decode则是相应的解码过程。这两个函数是为了提高深度值的精度,以便于进行深度值计算时不会产生太大误差。

5、_BackDepthTex是物体背面的深度图,由Camera.RenderWithShader()产生,这部分的代码在脚本中实现,读者可以参考完整示例。

 

下面给出完整示例程序:

 张明 2020-02-25 23楼 源码在GAD改版后丢失了,这里放百度网盘,需要的自取:链接:https://pan.baidu.com/s/1LBPycNqmlsmFFbLWzvkA7Q 提取码:tqar

标签:Unity3D,教程,lightDir,float,散射,float4,col,float3
From: https://www.cnblogs.com/gangtie/p/18497979

相关文章

  • Spring Boot 替换Word模板生成Word文件教程
    ......
  • Linux安装Redis(保姆教程)
    1,安装GCC依赖#sudo表示以管理员身份运行,如果使用的是管理员用户就不需要sudosudoyuminstall-ygcc2,添加EPEL仓库yuminstallepel-release#更新yum源yumupdate3,安装redisyuminstallredis4,查看redis安装的路径,默认安装路径为:/var/lib/redisfindI-nameredis5,修改......
  • 【网课下载教程】网课视频下载攻略:让学习更高效
    在当今互联网时代,在线学习已成为越来越多人的选择。有时,我们希望离线观看网课视频,以避免网络不稳定等问题。本文将为您提供一篇详细的网课视频下载教程,助您更高效地学习。一、为什么下载网课视频?无需依赖网络:下载后的视频可以在没有网络的情况下观看,便于在交通工具、户外等环境......
  • 公司网站页面文字修改?网站模板修改外观的教程?
    公司网站页面的文字修改是一项常见的维护任务,可以通过以下步骤来完成:确定修改内容:确定需要修改的具体页面和段落。准备好新的文字内容。访问网站管理后台:如果你的网站有CMS(内容管理系统)如WordPress、Drupal等,登录到后台管理界面。导航到需要修改的页面或文章。编......
  • 2024年最新最全傻瓜式教学青龙面板拉取,rabitpro短信登录,账密登录对接无界spy自动监控J
    上一期我们写了服务器宝塔面板搭建,青龙面板拉取并对接短信登录,没看过的小伙伴点传送门直达。这一期我们继续讲进阶教程无界spy对接tgbot自动监控JD线报自动运行开卡教程。从这里开始强烈建议拥有一台外网服务器,比如零成本的甲骨文云,申请需要一点小门槛,就是需要有一张visa或......
  • PDManer 入门教程:超强代码生成工具!
    操作手册说明:https://www.yuque.com/pdmaner/docs/pdmaner-manual下载地址说明:https://gitee.com/robergroup/pdmaner/releases开源博客介绍说明:4.0最新版说明https://my.oschina.net/skymozn/blog/5515012PDman2.2.0下载地址:http://www.downza.cn/soft/278049.htmlPD......
  • autMan奥特曼机器人对接新千寻Pro微信框架详细教程
    文件下载1、安装指定版本微信https://www.123865.com/s/3Wd9-q13jH需要的插件下载:https://pan.quark.cn/s/c1f10f726b6a2、最新千寻pro下载https://www.123865.com/s/3Wd9-Y43jH框架教程1、安装上面的指定微信版本,跟最新的千寻框架,然后启动框架双击千寻微信框架Pro......
  • (分享源码)计算机毕业设计必看必学 上万套实战教程手把手教学JAVA、PHP,node.js,C++、pyth
    摘 要大数据时代下,数据呈爆炸式地增长。为了迎合信息化时代的潮流和信息化安全的要求,利用互联网服务于其他行业,促进生产,已经是成为一种势不可挡的趋势。在网络小说的要求下,开发一款整体式结构的小说网站,将复杂的系统进行拆分,能够实现对需求的变化快速响应、系统稳定性的保......
  • 计算机毕业设计项目推荐,基于协同过滤算法的短视频推荐系统设计与实现30213(开题答辩+程
    摘 要现阶段,社会的发展和科技的进步,以及大数据时代下纷繁数据信息的融合,使得人们在生产及生活过程中,都将会接收到各种类型的数据信息,而通过计算机技术与网络技术,则能够将众多人们所不了解或不常用的信息,以简单的模式转化并传递给人们,使得人们的生产及生活质量得以显著提升......
  • 计算机毕业设计项目推荐:基于Web的社区人员管理系统的设计36303(开题答辩+程序定制+全套
    摘要科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代。在现实运用中,应用软件的工作规则和开发步骤,采用ASP.NET技术建设社......