前言
曾使用过UE5的substrate系统基于BSDF实现过玉石材质,效果雀氏nice但消耗太高了!因此本篇基于Unity介绍如何模拟透射来实现一个低功耗的玉石材质
效果如下
本篇同步发布于http://chenglixue.top/index.php/unity/90/
总体框架
模拟透射光
-
思路
-
因为透射现象是一种光打在物体发生散射,其中一部分光进入物体内部,且这一部分的光的某一部分成功穿过物体背面,最终我们便可以看到下图现象,所以很明显为了模拟这一现象,需要考虑相机方向向量 和 光源方向向量
-
但仅仅考虑相机方向向量和光源方向向量还不够,因为光线发生散射时,进入物体内部的光线方向是随机的,所以需要想办法对原光线进行扰动
-
最后还需考虑厚度对透射的影响
-
实现
模拟透射
-
可以通过扰动法线方向来模拟散射光线
// 模拟散射光线 float3 scatterMainLightDirw = -normalize(mainLightDirW + normalW * _DistortMainLightDir);
-
随后需要考虑相机方向向量 和 光源方向向量
// 对透射物体的光线向量和视角向量进行dot float VoInvL = saturate(dot(viewDirW, scatterMainLightDirw)); // pow控制透射对比度 // _MainLightScatterIntensity控制透射强度 VoInvL = pow(VoInvL, _Contrast) * _MainLightScatterIntensity;
下图展示了透射的对比度
-
计算透射颜色
// 模型的厚度贴图.值越高,越厚,越不容易透射 float thickness = SAMPLE_TEXTURE2D(_ThicknessMap, sampler_ThicknessMap, psInput.uv).r; // _ScatterColor.rgb控制透射颜色 half3 scatterColor = mainLightColor * VoInvL * (1 - thickness) * _ScatterColor.rgb;
漫反射
-
模拟的透射光并没有乘上diffuse纹理,若不添加diffuse计算会导致背对光源看向模型,模型是纯黑的。并添加一个_AddColor来手动调整玉石颜色
half4 diffuseTex = SAMPLE_TEXTURE2D(_DiffuseMap, sampler_DiffuseMap, psInput.uv) * _DiffuseTint; // half lambert float NoL = saturate(dot(normalW, mainLightDirW) * 0.5 + 0.5); half3 diffuseColor = diffuseTex * mainLightColor * NoL; diffuseColor += _AddColor.rgb; // 手动添加的额外颜色
环境光反射
-
因为是反射,所以可以添加fresnel效果进行丰富
TEXTURECUBE(_CubeMap); SAMPLER(sampler_CubeMap); float4 _CubeMap_HDR; float Fresnel(float VoN, float Power) { return pow(1.0 - saturate(VoN), Power); } float3 fresnel = Fresnel(VoN, _FresnelExp); // 环境光反射 half4 cubemapColor = SAMPLE_TEXTURECUBE(_CubeMap, sampler_CubeMap, reflectDirW); // 解码HDR half3 environmentColor = DecodeHDREnvironment(cubemapColor, _CubeMap_HDR) * fresnel * _Exposure;
模拟天光
-
这里模拟的天光也就是一个类似AO的效果
half3 skyLightColor = saturate(dot(normalW, float3(0, 1, 0) * 0.5 + 0.5)) * diffuseTex * _SkyLightOpacity;
多光源
-
这里只考虑额外光源的透射效果即可(这样美术效果更为合适)
[KeywordEnum(OFF, ON)]_ADDITION_LIGHT("Addition Light ?", Int) = 1 #pragma shader_feature_local _ADDITION_LIGHT_ON _ADDITION_LIGHT_OFF #if defined _ADDITION_LIGHT_ON // 额外光源的数量 int addLightCounts = GetAdditionalLightsCount(); for(int i = 0; i < addLightCounts; ++i) { Light addLight = GetAdditionalLight(i, psInput.positionW); float3 addLightDirW = normalize(addLight.direction); // 额外光源的方向 half3 addLightColor = addLight.color; // 额外光源的颜色 // 模拟散射光 float3 scatterAddLightDirw = -normalize(addLightDirW + normalW * _DistortAddLightDir); float addLightVoInvL = saturate(dot(viewDirW, scatterAddLightDirw)); addLightVoInvL = pow(addLightVoInvL, _Contrast) * _AddLightScatterIntensity; half3 addLightScatterColor = addLightColor * (1 - thickness) * addLightVoInvL * addLight.distanceAttenuation * addLight.shadowAttenuation; outputColor += addLightScatterColor; } #endif
Bloom + GT Tonemapping
- 这个实现在之前的文章实现过,可以看这http://chenglixue.top/index.php/unity/73/
全部代码
-
Shader
{ Properties { [Header(Diffuse Setting)] [Space(3)] [MainTexture]_DiffuseMap("Diffuse Texture", 2D) = "white" {} [HDR][MainColor]_DiffuseTint("Diffuse Color Tint", Color) = (1, 1, 1, 1) [HDR]_AddColor("Additional Color", Color) = (1, 1, 1, 1) [Space(30)] [Header(Light Setting)] _DistortMainLightDir("Distort Main Light Direction", Range(0, 1)) = 0 _DistortAddLightDir("Distort Add Light Direction", Range(0, 1)) = 0 _Contrast("Contrast", Float) = 5 _MainLightScatterIntensity("Main Light Scatter Intensity", Float) = 1 _AddLightScatterIntensity("Addition Light Scatter Intensity", Float) = 1 [HDR]_ScatterColor("Scatter Color", Color) = (1, 1, 1, 1) _SkyLightOpacity("SkyLight Opacity", Range(0, 1)) = 0 [KeywordEnum(OFF, ON)]_ADDITION_LIGHT("Addition Light ?", Int) = 1 [Space(30)] [Header(Thickness Setting)] _ThicknessMap("Thickness Tex", 2D) = "white" {} [Space(30)] [Header(Environment Setting)] _CubeMap("Cube map", Cube) = "white" {} _Exposure("Exposure", Range(0, 8)) = 1 _Rotation("Rotation",Range(0,360)) = 0 _FresnelExp("Fresnel Exp", Range(1, 10)) = 5 } SubShader { Tags { "Pipeline" = "UniversalPipeline" "RenderType" = "Opaque" "Queue" = "Geometry" } LOD 100 HLSLINCLUDE ENDHLSL Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #pragma shader_feature_local _ADDITION_LIGHT_ON _ADDITION_LIGHT_OFF #pragma multi_compile __ _MAIN_LIGHT_SHADOWS // 计算阴影衰减 #pragma multi_compile __ _MAIN_LIGHT_SHADOWS_CASCADE // 得到正确的阴影坐标 #pragma multi_compile __ _SHADOWS_SOFT //计算软阴影 #pragma multi_compile __ ADDITIONAL_LIGHT_CALCULATE_SHADOWS // 计算额外光的阴影衰减和距离衰减 #pragma multi_compile __ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS //计算阴影投射 #include_with_pragmas "Assets/Jade/Library/Jade.hlsl" #pragma vertex VS #pragma fragment PS ENDHLSL } Pass { Tags { "LightMode" = "ShadowCaster" } HLSLPROGRAM #include_with_pragmas "Assets/Jade/Library/Jade.hlsl" #pragma vertex VSShadow #pragma fragment PSShadow ENDHLSL } } }
-
HLSL
#pragma once #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl" // 个人的工具库 #include "Assets/Shader/MyUtil/MyUtil.hlsl" // -------------------------------------------- variable definition -------------------------------------------- CBUFFER_START(UnityPerMaterial) float4 _DiffuseMap_ST; half4 _DiffuseTint; half4 _AddColor; float _DistortMainLightDir; float _DistortAddLightDir; float _Contrast; float _MainLightScatterIntensity; float _AddLightScatterIntensity; half4 _ScatterColor; float _Exposure; float _Rotation; float _FresnelExp; float _SkyLightOpacity; // 获取主光源和其余光源的方向(Unity自动完成赋值) half3 _LightDirection; CBUFFER_END TEXTURE2D(_DiffuseMap); SAMPLER(sampler_DiffuseMap); TEXTURE2D(_ThicknessMap); SAMPLER(sampler_ThicknessMap); TEXTURECUBE(_CubeMap); SAMPLER(sampler_CubeMap); float4 _CubeMap_HDR; struct VSInput { float4 positionL : POSITION; float3 normalL : NORMAL; float2 uv : TEXCOORD0; }; struct PSInput { float4 positionH : SV_POSITION; float4 shadowUV : TEXCOORD3; float3 normalW : TEXCOORD0; float3 positionW : TEXCOORD1; float2 uv : TEXCOORD2; }; // -------------------------------------------- function definition -------------------------------------------- PSInput VS(VSInput vsInput) { PSInput vsOutput; // Get pos VertexPositionInputs vertexPosInput = GetVertexPositionInputs(vsInput.positionL); vsOutput.positionH = vertexPosInput.positionCS; vsOutput.positionW = vertexPosInput.positionWS; // Get normal VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(vsInput.normalL); vsOutput.normalW = vertexNormalInput.normalWS; // Get uv vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _DiffuseMap); vsOutput.shadowUV = TransformWorldToShadowCoord(vsOutput.positionW); return vsOutput; } half4 PS(PSInput psInput) : SV_TARGET { half3 outputColor = 0.f; // about texture half4 diffuseTex = SAMPLE_TEXTURE2D(_DiffuseMap, sampler_DiffuseMap, psInput.uv) * _DiffuseTint; float thickness = SAMPLE_TEXTURE2D(_ThicknessMap, sampler_ThicknessMap, psInput.uv).r; // about light Light mainLight = GetMainLight(psInput.shadowUV); float3 mainLightDirW = normalize(mainLight.direction); half3 mainLightColor = mainLight.color; // about direction float3 normalW = normalize(psInput.normalW); float3 viewDirW = normalize(GetCameraPositionWS() - psInput.positionW); // 模拟散射光线 float3 scatterMainLightDirw = -normalize(mainLightDirW + normalW * _DistortMainLightDir); // 环境光反射 float3 reflectDirW = normalize(reflect(-viewDirW, normalW)); reflectDirW = RotatEnvironment(_Rotation, reflectDirW); float NoL = saturate(dot(normalW, mainLightDirW) * 0.5 + 0.5); float VoN = saturate(dot(viewDirW, normalW)); float VoInvL = saturate(dot(viewDirW, scatterMainLightDirw)); // 模拟透射现象 VoInvL = pow(VoInvL, _Contrast) * _MainLightScatterIntensity; float3 fresnel = Fresnel(VoN, _FresnelExp); // result color // diffuse half3 diffuseColor = diffuseTex * mainLightColor * NoL; diffuseColor += _AddColor.rgb; // 手动添加的额外颜色 // 模拟透射光 half3 scatterColor = mainLightColor * VoInvL * (1 - thickness) * _ScatterColor.rgb; // 环境光反射 half4 cubemapColor = SAMPLE_TEXTURECUBE(_CubeMap, sampler_CubeMap, reflectDirW); half3 environmentColor = DecodeHDREnvironment(cubemapColor, _CubeMap_HDR) * fresnel * _Exposure; // 模拟天光 half3 skyLightColor = saturate(dot(normalW, float3(0, 1, 0) * 0.5 + 0.5)) * diffuseTex * _SkyLightOpacity; #if defined _ADDITION_LIGHT_ON int addLightCounts = GetAdditionalLightsCount(); for(int i = 0; i < addLightCounts; ++i) { Light addLight = GetAdditionalLight(i, psInput.positionW, half4(1.f, 1.f, 1.f, 1.f)); float3 addLightDirW = normalize(addLight.direction); half3 addLightColor = addLight.color; // 模拟散射光 float3 scatterAddLightDirw = -normalize(addLightDirW + normalW * _DistortAddLightDir); float addLightVoInvL = saturate(dot(viewDirW, scatterAddLightDirw)); addLightVoInvL = pow(addLightVoInvL, _Contrast) * _AddLightScatterIntensity; half3 addLightScatterColor = addLightColor * (1 - thickness) * addLightVoInvL * addLight.distanceAttenuation * addLight.shadowAttenuation; outputColor += addLightScatterColor; } #endif outputColor += diffuseColor + scatterColor + environmentColor + skyLightColor; return half4(outputColor, 1.f); } PSInput VSShadow(VSInput vsInput) { PSInput VSOutput; VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _DiffuseMap); VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL); VSOutput.positionW = TransformObjectToWorld(vsInput.positionL); Light mainLight = GetMainLight(); VSOutput.positionH = TransformWorldToHClip(ApplyShadowBias(VSOutput.positionW, VSOutput.normalW, _LightDirection)); #if UNITY_REVERSED_Z VSOutput.positionH.z = min(VSOutput.positionH.z, VSOutput.positionH.w * UNITY_NEAR_CLIP_VALUE); #else VSOutput.positionH.z = max(VSOutput.positionH.z, VSOutput.positionH.w * UNITY_NEAR_CLIP_VALUE); #endif return VSOutput; } half4 PSShadow(PSInput psInput) : SV_TARGET { return 0; }