Unity中的仿真丝袜渲染

Wesley13
• 阅读 1225

Stocking Rendering in Unity

前言

一年前曾尝试过这个课题,研究的比较浅,最终效果也一般。最近重新搬出这个课题研究,以期获得更令人满意的结果。
本文会在Unity的PBR基础上,依据真实的物理原理尝试对丝袜这种物品给出一种仿真的渲染方法。原理和计算方面还是比较简单的,但是也需要一些SurfaceShader基础。

相关工程文件下载在文末

物理层面的分析

在开始编写Shader前,首先我们需要对丝袜这种物品在现实中的物理性质进行一个具体的分析。
丝袜有着众多的款式,形状、功能、材料各有不同,依据我在百度百科和各类淘宝爆款中丰富的调研,最终我选择了其中最具有代表性,也是现今最常见的尼龙丝袜进行分析。

Unity中的仿真丝袜渲染

主要材质为 约90%聚酰胺纤维(锦纶/尼龙)+ 约10%聚氨酯纤维(氨纶)
基本上可以推测这种材质的金属性(Metallic)为0,而光滑度(Smoothness)则根据种类不同会有较大区别。

丹尼尔值

各种丝袜的厚薄也有区别,“D”或“Denier”是指纤维的纤度单位。5D、10D几乎透明,100D以上则几乎不透,很好理解,请看下图:

Unity中的仿真丝袜渲染

所以Shader中我们就以Denier值表示丝袜的厚薄程度。
机核网的相关文献:11月2日裤袜之日,是时候研究神秘的裤袜了

纤维的特性

我们可以发现一种现象——靠近腿部中央的位置丝袜颜色较淡,并且如果丝袜够薄甚至会显出底下皮肤的颜色,而越靠近腿两侧丝袜颜色则会越深。丝袜由无数细小交错的纤维组成,所以正对视线的纤维是最稀疏的,而越靠近腿部轮廓的纤维则对于观察者来说越显得密集。

Unity中的仿真丝袜渲染

能否通过渲染出每一根纤维实现丝袜的效果?
几乎不可行,就像游戏的毛发渲染也没有一根根渲染的一样,实际纤维的密集程度远超越现阶段电脑游戏的精度,虽然可以做出大概的意思,但是这些交错的线条会产生大量摩尔纹而影响观感。

Unity中的仿真丝袜渲染

简单尝试一下,效果可以说相当恶心了。不过扩大间距后似乎就接近了渔网袜的效果,当然这不是本文的目标…

摩尔纹
空间频率相近的两组图案相互干涉,会有更低频率(更宽间距)的图案显示出来。
使用相机拍摄屏幕的照片和扫描图上常常可以看见,游戏中因为像素点有限,在光栅化过程中也会出现。

开始吧

既然基于PBR,新建一个Surface Shader文件,然后依据需求稍微删改一下:

Shader "Custom/NewSurfaceShader" {
    Properties {
        _NormalTex("Normal", 2D) = "bump"{}
        _Smoothness("Smoothness", Range(0,1)) = 0.5
    }
    SubShader {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _NormalTex;

        struct Input {
            float2 uv_NormalTex;
        };

        half _Smoothness;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            o.Albedo = 1;
            o.Normal = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex));
            o.Metallic = 0;
            o.Smoothness = _Smoothness;
            o.Alpha = 1;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

删除了金属度等一些不需要的属性,加入了法线。
我们就以此为基础,开始我们的丝袜探索之旅吧。

计算

对丝袜纤维的密集程度定义一个属性 疏密度(Density) ,取值范围0(几乎没有纤维)到 1(完全被纤维覆盖)。依据疏密度提供颜色给Albedo通道,疏密度越高显现更多丝袜颜色,越低则显现皮肤颜色。

计算疏密度需要三部步骤:

  1. 丹尼尔值对整体疏密度的影响。

  2. 受丝袜良好弹性影响,需要一张灰度贴图来控制不同区域的拉伸程度,拉伸越大疏密度越低。

  3. 受观察者视线的影响,丝袜平面与观察者视线角度越小时,疏密度越大。称其为边缘度影响。

对于这几点,我们定义几个属性:

Properties{
    _Denier("Denier", Range(5,120)) = 25.0
    _DenierTex("Density Texture", 2D) = "black"{}
    [Enum(Strong,6,Normal,12,Weak,20)] _RimPower("Rim Power", float) = 12
    _SkinTint("Skin Color Tint", Color) = (1,0.9,0.8,1)
    _SkinTex("Skin Color", 2D) = "white" {}
    _StockingTint("Stocking Color Tint", Color) = (1,1,1,1)
    _StockingTex("Stocking Color", 2D) = "white"{}
}

(1),(2) 丹尼尔值与拉伸程度贴图

float denier = (_Denier - 5) / 115;
float density = denier * (1 - tex2D(_DenierTex, IN.uv_DenierTex));

_Denier的数值进行归一化(Normalize)。_DenierTex是一张灰度贴图,越淡的地方表示丝袜被拉伸的程度越高,其纤维越稀疏相应会显示更多皮肤颜色。两者相乘。

Unity中的仿真丝袜渲染

如示例,一般情况下认为膝盖部分和大腿越靠根部的地方会拉扯的更加厉害。
_Denier 一般只在没有_DenierTex 贴图的情况下做调整用,使用_DenierTex 时请保证_Denier 值为最大(120)以正确还原贴图的效果。

(3) 边缘度的计算

有一种很常见的被称为 Rim Lighting 的效果,简单来说就是模型边缘的内发光效果,简单来说就是模型边缘的内发光效果,我们可以参考其中计算边缘程度的代码。相关代码在Unity官方文档的Surface Shader examples中就可以找到。

根据自己的需求改写:

float rim = pow(1 - dot(normalize(IN.viewDir), o.Normal), _RimPower / 10);

通过点乘(dot)计算视线方向和法线方向的数量积。视线方向与法线方向完全相反时,即丝袜最中心的部分,取得值为1;而最侧边视线方向与法线方向垂直时,取得值为0。因为我们定义的是边缘度,所以应该是越靠边缘值越大,用1与其相减获得正确的数值。最后对数值进行了**(_RimPower / 10)** 次方,实际应用时,依据效果调整_RimPower,使边缘度更符合现实的情况。

最终混合颜色

首先需要计算出皮肤颜色(SkinColor) 和 **丝袜颜色(StockingColor)**,很简单不多解释。

float4 skinColor = tex2D(_SkinTex, IN.uv_SkinTex) * _SkinTint;
float4 stockingColor = tex2D(_StockingTex, IN.uv_StockingTex) * _StockingTint;

依据疏密度对这两个颜色进行简单的混合,疏密度越高显现更多丝袜颜色,越低则显现皮肤颜色,很简单的lerp即可。

lerp(skinColor, stockingColor, density); 

完整Shader

Shader "Custom/Stocking" {
    Properties{
        _Denier("Denier", Range(5,120)) = 25.0
        _DenierTex("Density Texture", 2D) = "black"{}
        [Enum(Strong,6,Normal,12,Weak,20)] _RimPower("Rim Power", float) = 12
        _SkinTint("Skin Color Tint", Color) = (1,0.9,0.8,1)
        _SkinTex("Skin Color", 2D) = "white" {}
        _StockingTint("Stocking Color Tint", Color) = (1,1,1,1)
        _StockingTex("Stocking Color", 2D) = "white"{}
        _NormalTex("Normal", 2D) = "bump"{}
        _Smoothness("Smoothness", Range(0,1)) = 0.1
    }
    SubShader{
        Tags{ "RenderType" = "Opaque" }

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        struct Input {
            float2 uv_SkinTex;
            float2 uv_StockingTex;
            float2 uv_DenierTex;
            float2 uv_NormalTex;
            float3 viewDir;
        };

        float _RimPower;
        float _Denier;
        float _Smoothness;
        float4 _SkinTint;
        float4 _StockingTint;
        sampler2D _DenierTex;
        sampler2D _SkinTex;
        sampler2D _StockingTex;
        sampler2D _NormalTex;

        void surf(Input IN, inout SurfaceOutputStandard o) {
            o.Normal = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex));

            float4 skinColor = tex2D(_SkinTex, IN.uv_SkinTex) * _SkinTint;
            float4 stockingColor = tex2D(_StockingTex, IN.uv_StockingTex) * _StockingTint;
            float rim = pow(1 - dot(normalize(IN.viewDir), o.Normal), _RimPower / 10);
            float denier = (_Denier - 5) / 115;
            float density = max(rim, (denier * (1 - tex2D(_DenierTex, IN.uv_DenierTex))));
            
            o.Albedo = lerp(skinColor, stockingColor, density);
            o.Metallic = 0;
            o.Smoothness = _Smoothness;
        }
    ENDCG
    }
    FallBack "Diffuse"
}

后处理

屏幕空间次表面散射 (SSSSS)

SSSSS你可能不知道,但是SSS你应该听过,就是常说的3S材质。这个是在Github上找到的,利用屏幕空间实现的SSS效果。
最后使用Unity官方的Post Processing Stack对画面进行调整也必不可少的。

链接:
Custom Phase Screen-Space Subsurface Scattering
Post Processing Stack

最终效果

Unity中的仿真丝袜渲染

项目文件

在我的Coding上:
https://coding.net/u/gypsum/p/Unity-UltraStocking/git
里面还有一个2B夏装的模型,本来是自己准备调一下效果的,后来觉得麻烦算了...

作者:石膏 來源:简书

Unity中的仿真丝袜渲染

本文分享自微信公众号 - Unity3D游戏开发精华教程干货(u3dnotes)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
Souleigh ✨ Souleigh ✨
3年前
Next.js页面渲染的优化方案
Next.js页面渲染的优化方案在过去一年的工作中我所使用的js框架是Next.js,尽管这个框架在前后端同构方面有着绝佳的体验,但是当页面js文件过大以及preload过多的时候还是会出现页面跳转卡顿和渲染阻塞等比较糟糕的用户体验问题。由于我之前既不知道这个框架的工作原理,自然也就不知道如何去优化它。乘着农历春节前工地
Stella981 Stella981
3年前
Python从零实现区块链仿真【含源码】
在区块链或数字货币领域,Python并不是主流的开发语言。但是如果你的目的是研究区块链技术的原理,或者需要在自己的笔记本上仿真一个区块链网络并进行一些研究性的实验,比如完成自己的毕业设计项目或科研课题,那么Python就是合适的。在这个教程里,我们将学习如何使用Python从零开发一个多节点的区块链网络,并基于这个仿真区块链网络,开发一个去中心化的数据分享应
Wesley13 Wesley13
3年前
vite2 引入 vectorize
这个库在webpack中是正常的,但是在vite2项目中无法使用也不报错,只是结果总是空....看了下是个老项目了,依赖的也都是好几年没更新的库...看来webpack在兼容性方面还是要强不少的    尝试使用parcel打包后使用,不太行...用webpack打包后使用....也不行...有时间再研究研究,看看能不能写个类似的.
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
Unity
简介URP是一种预置的可编程渲染管线。可以实现快速的渲染而不需要shader技术。URP使用简化的基于物理的光照和材质。URP继承自’RenderPipelineAsset’,当我们将URP设置到GraphicsSetting时,Unity将内置的渲染管线转换到URP。在URP中可以对下面一些对象进行设置:General
Wesley13 Wesley13
3年前
unity将 -u4E00 这种 编码 转汉字 方法
 unity中直接使用 JsonMapper.ToJson(对象),取到的字符串,里面汉字可能是\\u4E00类似这种其实也不用转,服务器会通过类似fastjson发序列化的方式,将json转对象,获取对象的值就是中文但是有时服务器要求将传参中字符串中类似\\u4E00这种转汉字,就需要下面 publ
Wesley13 Wesley13
3年前
.NET 文件格式相关开源项目
在过去的2年里,我已确定把文件格式作为我的主要研究课题之一,NPOI就是在这样的研究课题下的产物。尽管从严格意义上讲NPOI只是POI的.NET版本,并不是我创造的,但是在开发过程中,我对OLE2有了深入的理解和认识,也对Office972003的文件格式有了截然不同的认识。如果在过去,有人问我:你知道Office文件格式吗?作为开发人员的我会毫不犹豫的
Stella981 Stella981
3年前
Chrome扩展程序一键生成网页骨架屏
对于依赖接口渲染的页面,在拿到数据之前页面往往是空白的,为了提示用户当前正在加载中,往往会使用进度条、loading图标或骨架屏的方式。对于前两种方案而言,实现比较简单;本文主要研究骨架屏的应用及实现,并给出一种使用Chrome扩展工具快速生成骨架屏的方案。首先看看效果先放一个动图展示!(https://oscimg.oschina
计算机视觉与信息取证技术讲解
今晚20:0022:00人工智能技术与自信计算机视觉就是用各种成像系统代替视觉器官作为输入敏感手段,由计算机来代替大脑完成处理和解释。计算机视觉的最终研究目标就是使计算机能象人那样通过视觉观察和理解世界,具有自主适应环境的能力。要经过长期的努力才能达到的目标。因此,在实现最终目标以前,人们努力的中期目标是建立一种视觉系统,这个系统能依据视觉敏感和反馈的某
京东云开发者 京东云开发者
7个月前
React memo的原理、实践与思考
前言在react中,对一个组件进行点击事件等操作时,该组件以及该组件的子组件都会重新渲染。避免组件的重新渲染一般可以借助React.memo、useCallback等来实现。什么是memomemo原理memo类似于class中pureComponent的特