Shader assembly of fresnel approximation

本文介绍了一款由 hanecci 开发的基于不同优化方法的 Shader Fresnel 效果实现,包括无优化、Black Ops 2、Sébastien Lagarde 的球形高斯近似等,详细解释了每个方法的原理和应用。


//----------------------------------------------------------------------------------------------------
// Fresnel test shader by hanecci
//
// This software is released under the Unlicense. ( http://unlicense.org/ )
//
// This is free and unencumbered software released into the public domain.
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any means.
//
//----------------------------------------------------------------------------------------------------

in vec3         normal;
in vec3         half_vec;
uniform vec3    f0;

#define FRESNEL_MODE       ( 0 )

#define FRESNEL_NO_OPT     ( 0 )
#define FRESNEL_BLACK_OPS2 ( 1 )
#define FRESNEL_SEBASTEIN0 ( 2 )
#define FRESNEL_SEBASTEIN1 ( 3 )
#define FRESNEL_DISNEY	   ( 4 )

void main( void )
{
	vec4 color = vec4( 0.0, 0.0, 0.0, 0.0 );

	float dot_n_h = dot( normal, half_vec );

#if ( FRESNEL_MODE == FRESNEL_NO_OPT )

	color.rgb = f0 + ( vec3(1.0) - f0 ) * pow( dot_n_h, 5 );

#elif ( FRESNEL_MODE == FRESNEL_BLACK_OPS2 )

	color.rgb = f0 + ( vec3(1.0) - f0 ) * exp2( - 10.0 * dot_n_h );

#elif ( FRESNEL_MODE == FRESNEL_SEBASTEIN0 )

	color.rgb = f0 + ( vec3(1.0) - f0 ) * exp2( - 8.35 * dot_n_h );

#elif ( FRESNEL_MODE == FRESNEL_SEBASTEIN1 )

	color.rgb = f0 + ( vec3(1.0) - f0 ) * exp2( ( - 5.55473 * dot_n_h - 6.98316 ) * dot_n_h );	

#elif ( FRESNEL_MODE == FRESNEL_DISNEY )

	float value2 = dot_n_h * dot_n_h;
	float value5 = value2 * value2 * dot_n_h;
	color.rgb    = mix( f0, vec3(1), value5 );

#endif

	gl_FragColor = color;
}


No optimization
; --------  Disassembly --------------------
00 ALU: ADDR(32) CNT(16) 
      0  x: DOT4        ____,  R0.x,  R1.x      
         y: DOT4        ____,  R0.y,  R1.y      
         z: DOT4        ____,  R0.z,  R1.z      
         w: DOT4        ____,  (0x80000000, -0.0f).x,  0.0f      
         t: ADD         R0.y, -C0.x,  1.0f      
      1  x: ADD         R0.x, -C0.y,  1.0f      
         w: ADD         R0.w, -C0.z,  1.0f      
         t: LOG_sat     ____,  PV0.x      
      2  w: MUL         ____,  PS1,  (0x40A00000, 5.0f).x      
         t: MOV         R1.w,  0.0f      
      3  t: EXP_e       ____,  PV2.w      
      4  x: MULADD      R1.x,  PS3,  R0.y,  C0.x      
         y: MULADD      R1.y,  PS3,  R0.x,  C0.y      
         z: MULADD      R1.z,  PS3,  R0.w,  C0.z      
01 EXP_DONE: PIX0, R1
END_OF_PROGRAM

"Black Ops 2" @ SIGGRAPH 2013

f:id:hanecci:20140423024227j:image:w360

; --------  Disassembly --------------------
00 ALU: ADDR(32) CNT(15) 
      0  x: DOT4        ____,  R0.x,  R1.x      
         y: DOT4        ____,  R0.y,  R1.y      
         z: DOT4        ____,  R0.z,  R1.z      
         w: DOT4        ____,  (0x80000000, -0.0f).x,  0.0f      
         t: ADD         R0.y, -C0.x,  1.0f      
      1  x: ADD         R0.x, -C0.y,  1.0f      
         y: ADD         R1.y, -C0.z,  1.0f      
         w: MUL         ____,  PV0.x,  (0xC1200000, -10.0f).x      
         t: MOV         R2.w,  0.0f      
      2  t: EXP_e       ____,  PV1.w      
      3  x: MULADD      R2.x,  PS2,  R0.y,  C0.x      
         y: MULADD      R2.y,  PS2,  R0.x,  C0.y      
         z: MULADD      R2.z,  PS2,  R1.y,  C0.z      VEC_021 
01 EXP_DONE: PIX0, R2
END_OF_PROGRAM

"Approximate Fresnel with Spherical Gaussian"
Version0

f:id:hanecci:20140426204406j:image:w360

; --------  Disassembly --------------------
00 ALU: ADDR(32) CNT(15) 
      0  x: DOT4        ____,  R0.x,  R1.x      
         y: DOT4        ____,  R0.y,  R1.y      
         z: DOT4        ____,  R0.z,  R1.z      
         w: DOT4        ____,  (0x80000000, -0.0f).x,  0.0f      
         t: ADD         R0.y, -C0.x,  1.0f      
      1  x: ADD         R0.x, -C0.y,  1.0f      
         y: ADD         R1.y, -C0.z,  1.0f      
         w: MUL         ____,  PV0.x,  (0xC105999A, -8.350000381f).x      
         t: MOV         R2.w,  0.0f      
      2  t: EXP_e       ____,  PV1.w      
      3  x: MULADD      R2.x,  PS2,  R0.y,  C0.x      
         y: MULADD      R2.y,  PS2,  R0.x,  C0.y      
         z: MULADD      R2.z,  PS2,  R1.y,  C0.z      VEC_021 
01 EXP_DONE: PIX0, R2
END_OF_PROGRAM


Version1

f:id:hanecci:20140426204407j:image:w360

f:id:hanecci:20130728002327j:image:w480

; --------  Disassembly --------------------
00 ALU: ADDR(32) CNT(16) 
      0  x: DOT4        ____,  R0.x,  R1.x      
         y: DOT4        R0.y,  R0.y,  R1.y      
         z: DOT4        ____,  R0.z,  R1.z      
         w: DOT4        ____,  (0x80000000, -0.0f).x,  0.0f      
         t: ADD         R1.y, -C0.x,  1.0f      
      1  x: MULADD      R127.x,  PV0.x,  (0xC0B1C059, -5.554729939f).y,  (0xC0DF760C, -6.983160019f).x      
         y: ADD         R2.y, -C0.y,  1.0f      
         w: ADD         R0.w, -C0.z,  1.0f      
         t: MOV         R3.w,  0.0f      
      2  w: MUL         ____,  R0.y,  PV1.x      
      3  t: EXP_e       ____,  PV2.w      
      4  x: MULADD      R3.x,  PS3,  R1.y,  C0.x      
         y: MULADD      R3.y,  PS3,  R2.y,  C0.y      VEC_021 
         z: MULADD      R3.z,  PS3,  R0.w,  C0.z      
01 EXP_DONE: PIX0, R3
END_OF_PROGRAM

Fresnel at Disney BRDF
# Copyright Disney Enterprises, Inc.  All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License
# and the following modification to it: Section 6 Trademarks.
# deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the
# trade names, trademarks, service marks, or product names of the
# Licensor and its affiliates, except as required for reproducing
# the content of the NOTICE file.
#
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0

float SchlickFresnel(float u)
{
    float m = clamp(1-u, 0, 1);
    float m2 = m*m;
    return m2*m2*m; // pow(m,5)
}

float FH = SchlickFresnel(LdotH);
vec3 Fs = mix(Cspec0, vec3(1), FH);

; --------  Disassembly --------------------
00 ALU: ADDR(32) CNT(15) 
      0  x: DOT4        ____,  R0.x,  R1.x      
         y: DOT4        R127.y,  R0.y,  R1.y      
         z: DOT4        ____,  R0.z,  R1.z      
         w: DOT4        ____,  (0x80000000, -0.0f).x,  0.0f      
         t: ADD         R126.y, -C0.x,  1.0f      
      1  x: MUL         ____,  PV0.x,  PV0.x      
         y: ADD         R125.y, -C0.y,  1.0f      
         w: ADD         R127.w, -C0.z,  1.0f      
         t: MOV         R0.w,  0.0f      
      2  w: MUL         ____,  PV1.x,  PV1.x      
      3  z: MUL         ____,  R127.y,  PV2.w      
      4  x: MULADD      R0.x,  R126.y,  PV3.z,  C0.x      
         y: MULADD      R0.y,  R125.y,  PV3.z,  C0.y      VEC_120 
         z: MULADD      R0.z,  R127.w,  PV3.z,  C0.z      
01 EXP_DONE: PIX0, R0
END_OF_PROGRAM
http://d.hatena.ne.jp/hanecci/20140412/p4

<think>我们被要求实现一个URP下的菲涅尔透明效果的Shader。根据引用内容,菲涅尔效果的关键在于计算物体表面的法线向量(N)与视线向量(V)之间的角度。菲涅尔效应的公式通常为:$Fresnel = pow(1.0 - saturate(dot(N, V)), Power)$,其中Power控制效果的强度。 在URP中,我们需要获取世界空间下的法线向量和视线向量(从表面点到摄像机的方向)。根据引用[1]和引用[2],我们可以总结出以下步骤: 1. 在顶点着色器中,将顶点的位置转换到世界空间。 2. 获取世界空间下的法线向量(N)。注意,引用[2]提到需要将法线向量归一化。 3. 获取世界空间下的视线向量(V),即从顶点指向摄像机的向量,并归一化。 4. 在片元着色器中,计算N·V(点积),然后应用菲涅尔公式。 此外,我们还需要实现透明效果。菲涅尔效果通常用于控制透明度,即菲涅尔值大的地方(边缘)更透明或更不透明(取决于需求)。这里我们假设我们希望物体边缘透明,中心不透明(例如,用于溶解效果或边缘消隐)。 具体步骤: - 使用URP的PBR着色器模板创建Shader。 - 在顶点着色器中计算世界空间位置和法线。 - 在顶点着色器中计算视线方向(从顶点指向摄像机)并传递给片元着色器(注意,为了在片元着色器中得到更精确的结果,也可以在片元着色器中计算)。 - 在片元着色器中,归一化法线和视线方向,计算菲涅尔值。 - 将菲涅尔值用作透明度(Alpha),例如:alpha = fresnel。 但是,注意透明效果需要设置渲染队列(Queue)和混合模式(Blend)。通常,我们可以使用透明渲染队列("Queue"="Transparent")和叠加混合(例如Blend SrcAlpha OneMinusSrcAlpha)。 同时,我们可能需要一个基础颜色和菲涅尔强度的参数。 根据引用[3],菲涅尔效果的原理是当视线与法线夹角越大(即边缘)时,菲涅尔值越大。 因此,我们可以设计一个Shader,其中: - 菲涅尔值高的地方(边缘)透明度高(即alpha值小),中心透明度低(alpha值大)。但注意,我们也可以反过来使用。 例如,我们可以用菲涅尔值直接作为alpha: ```hlsl alpha = fresnel; // 这样边缘透明,中心不透明 ``` 或者反过来: ```hlsl alpha = 1 - fresnel; // 中心透明,边缘不透明(不太常见) ``` 另外,为了控制效果,我们可以添加一个颜色属性,并可能使用菲涅尔值来混合颜色和透明。 下面是一个基本的Shader实现框架: 1. 属性定义: - `_BaseColor`:基础颜色 - `_FresnelPower`:菲涅尔指数,用于控制边缘的范围 - `_AlphaFactor`:可选,用于调整透明度的强度 2. 顶点着色器: - 将顶点位置转换到裁剪空间 - 计算世界空间位置和世界空间法线 - 计算世界空间视线方向(摄像机位置 - 顶点世界位置) 3. 片元着色器: - 归一化法线和视线方向 - 计算NdotV = dot(N, V) - 菲涅尔 = pow(1.0 - saturate(NdotV), _FresnelPower) - 将菲涅尔值作为alpha,也可以作为颜色系数 注意:在URP中,摄像机的位置可以通过宏`GetCameraPositionWS()`获得(需要包含`Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl`),或者使用`_WorldSpaceCameraPos`(在Unity中通常已经定义)。 在URP中,更推荐使用URP提供的函数。根据引用[1][2],我们可以使用`GetVertexPositionInputs`和`GetVertexNormalInputs`来获取位置和法线,但这通常在顶点输入中已经处理。 然而,为了简化,我们可以直接使用TransformObjectToWorld将顶点位置转换到世界空间,以及使用TransformObjectToWorldNormal将法线转换到世界空间。 在URP中,我们可以这样计算视线方向: ```hlsl float3 worldPos = TransformObjectToWorld(v.vertex.xyz); float3 worldViewDir = GetWorldSpaceViewDir(worldPos); // 这个函数在Core.hlsl中,返回未归一化的向量 ``` 或者手动计算: ```hlsl float3 worldViewDir = _WorldSpaceCameraPos.xyz - worldPos; ``` 然后,在片元着色器中归一化。 但是,引用[1][2]中提到在片元着色器中归一化可以得到更好的效果,所以我们可以在顶点着色器中传递世界位置和世界法线,然后在片元着色器中计算视线方向并归一化。 步骤总结: 1. 包含必要的头文件。 2. 定义顶点输入和输出结构体。 3. 在顶点着色器中计算世界位置、世界法线,并将它们传递给片元着色器。 4. 在片元着色器中,用世界位置和摄像机位置计算视线方向,归一化法线和视线方向,计算菲涅尔值。 5. 将菲涅尔值用于透明度。 下面是一个具体的Shader代码示例: ```hlsl Shader "Custom/FresnelTransparent" { Properties { _BaseColor ("Base Color", Color) = (1,1,1,1) _FresnelPower ("Fresnel Power", Range(0,10)) = 5.0 } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" } LOD 100 Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 vertex : POSITION; float3 normal : NORMAL; }; struct Varyings { float4 vertex : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPosition : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float _FresnelPower; CBUFFER_END Varyings vert (Attributes v) { Varyings o; o.vertex = TransformObjectToHClip(v.vertex.xyz); o.worldPosition = TransformObjectToWorld(v.vertex.xyz); o.worldNormal = TransformObjectToWorldNormal(v.normal); return o; } half4 frag (Varyings i) : SV_Target { // 归一化世界法线 float3 worldNormal = normalize(i.worldNormal); // 计算世界空间的视线方向(从表面点到摄像机) float3 worldViewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPosition.xyz); // 计算NdotV float NdotV = dot(worldNormal, worldViewDir); // 菲涅尔效果 float fresnel = pow(1.0 - saturate(NdotV), _FresnelPower); // 将菲涅尔值用作透明度,这里我们希望边缘透明,所以直接使用fresnel half4 col = _BaseColor; col.a = fresnel; // 边缘透明,中心不透明 return col; } ENDHLSL } } } ``` 注意: - 我们使用了URP的转换函数:`TransformObjectToHClip`,`TransformObjectToWorld`,`TransformObjectToWorldNormal`。 - 在片元着色器中,我们重新归一化了法线(因为插值可能会改变长度)和视线方向。 - 我们使用`_WorldSpaceCameraPos`来获取摄像机的位置,这是Unity内置变量。 - 混合模式设置为`Blend SrcAlpha OneMinusSrcAlpha`,这是最常见的透明混合方式。 - 关闭了深度写入(ZWrite Off),以避免透明物体的深度问题。 如果需要中心部分透明,而边缘不透明,可以将alpha改为`col.a = 1 - fresnel;`。 此外,我们可以调整颜色,例如将菲涅尔值也用于颜色增强边缘的颜色。 这个Shader可以实现一个简单的菲涅尔透明效果。根据需求,可能还需要添加更多的控制参数,例如边缘颜色等。 最后,我们生成相关问题: §§相关问题§§ 1. 如何调整菲涅尔效果的边缘宽度和强度? 2. 如何让菲涅尔效果在URP下同时影响颜色和透明度? 3. 在移动设备上如何优化菲涅尔效果的计算? 4. 如何处理透明物体的渲染排序问题? </think>以下是针对URP管线实现菲涅尔透明效果的Shader完整解决方案,结合引用内容的最佳实践: ### 💧 URP下菲涅尔透明效果Shader实现 ```hlsl Shader "Custom/FresnelTransparent" { Properties { _BaseColor ("Base Color", Color) = (1,1,1,1) _FresnelColor ("Fresnel Color", Color) = (1,0.5,0,1) _FresnelPower ("Fresnel Power", Range(0,10)) = 5.0 _AlphaMultiplier ("Alpha Multiplier", Range(0,5)) = 1.0 } SubShader { Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" struct Attributes { float4 vertex : POSITION; float3 normal : NORMAL; }; struct Varyings { float4 vertex : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 viewDir : TEXCOORD1; }; CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _FresnelColor; float _FresnelPower; float _AlphaMultiplier; CBUFFER_END // URP中获取归一化的世界空间视角方向[^1][^2] float3 GetWorldSpaceNormalizedViewDir(float3 worldPosition) { return normalize(_WorldSpaceCameraPos.xyz - worldPosition); } Varyings vert (Attributes v) { Varyings o; // 顶点位置变换 o.vertex = TransformObjectToHClip(v.vertex.xyz); // 获取世界空间法线 float3 worldPos = TransformObjectToWorld(v.vertex.xyz); o.worldNormal = TransformObjectToWorldNormal(v.normal); // 获取归一化的视角方向[^1] o.viewDir = GetWorldSpaceNormalizedViewDir(worldPos); return o; } half4 frag (Varyings i) : SV_Target { // 归一化向量 float3 N = normalize(i.worldNormal); float3 V = normalize(i.viewDir); // 核心菲涅尔计算 $F = (1 - \langle N,V\rangle)^P$[^3] float NdotV = saturate(dot(N, V)); float fresnel = pow(1.0 - NdotV, _FresnelPower); // 透明效果:边缘透明,中心不透明 float alpha = fresnel * _AlphaMultiplier; // 颜色混合:基础色+菲涅尔边缘光 half4 color = lerp(_BaseColor, _FresnelColor, fresnel); color.a = saturate(alpha); return color; } ENDHLSL } } } ``` ### 🔑 实现要点解析 1. **视角方向获取**: - 使用URP内置方法计算世界空间视角方向: $$ \vec{V} = \text{normalize}(\text{\_WorldSpaceCameraPos} - \text{worldPos}) $$ - 替代Built-in RP的`WorldSpaceViewDir`[^1][^2] 2. **菲涅尔核心计算**: ```hlsl float NdotV = saturate(dot(N, V)); float fresnel = pow(1.0 - NdotV, _FresnelPower); ``` - 基于法线与视角夹角的指数衰减 $F = (1 - \langle N,V\rangle)^P$[^3] - `_FresnelPower`控制边缘宽度 3. **透明效果控制**: ```hlsl float alpha = fresnel * _AlphaMultiplier; color.a = saturate(alpha); ``` - 菲涅尔值直接驱动透明度 - `_AlphaMultiplier`调节整体透明强度 4. **混合设置**: ```hlsl Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Tags { "Queue" = "Transparent" } ``` - 标准透明混合模式 - 禁用深度写入避免渲染异常 ### 🎨 效果调节指南 1. **基础效果**: ```csharp _FresnelPower = 3.0 // 中等边缘宽度 _AlphaMultiplier = 1.0 ``` 2. **全透明边缘**: ```csharp _FresnelPower = 8.0 // 锐利边缘 _AlphaMultiplier = 2.0 // 增强透明度 ``` 3. **彩色光晕效果**: ```csharp _FresnelColor = (1,0,0,1) // 红色光晕 _BaseColor = (0,0,0,0) // 中心完全透明 ``` ### ⚠️ 注意事项 1. 法线向量必须使用`TransformObjectToWorldNormal`正确转换[^2] 2. 大场景中需考虑使用视图空间计算解决精度问题 3. 移动平台优化建议: - 将菲涅尔计算移至顶点着色器 - 使用半精度变量(`half3`) ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值