티스토리 뷰
PBR (물리 기반 렌더링)은 Unity의 "Standard"셰이더가 사용하는 셰이딩/조명모델이며 셰이더 그래프의 URP "Lit"셰이더 및 PBR 마스터 노드입니다.
이전 섹션에서 언급했듯이 기본 제공 파이프 라인의 음영/조명은 일반적으로 "Standard"옵션이 PBR모델인 surface셰이더에 의해 처리되었습니다. 이들은 Albedo, Normal, Emission, Smoothness, Occlusion, Alpha 및 Metallic (또는 "StandardSpecular"워크 플로우를 사용하는 경우 Specular)을 출력하는 surface함수를 사용했습니다. Unity는 이를 가져와 scene뒤에서 vertex 및 fragment셰이더를 생성하여 PBR 셰이딩/조명 및 그림자와 같은 특정 계산을 처리합니다
Universal RP는 surface셰이더를 지원하지 않지만 ShaderLibrary는 많은 조명 계산을 처리하는 데 도움이되는 함수를 제공합니다. 이것들은 Lighting.hlsl에 포함되어 있습니다. 이 섹션에서는 UniversalFragmentPBR에 초점을 맞출 것입니다.
half4 UniversalFragmentPBR(InputData inputData, half3 albedo,
half metallic, half3 specular, half smoothness, half occlusion,
half3 emission, half alpha)
// v10.x.x에 추가 된 SurfaceData 구조체가있는 버전도 있습니다.
// 이전 버전의 경우 대신 위 버전을 사용해야합니다.
// (하지만 여전히 SurfaceData 구조체를 사용하여 데이터를 구성 / 보유 할 수 있습니다.)
half4 UniversalFragmentPBR(InputData inputData, SurfaceData surfaceData)
// There is also :
half4 UniversalFragmentBlinnPhong(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha)
// Unity v4 이전의 "이전"표면 셰이더를 복제합니다.
// URP의 "SimpleLit"셰이더에서 사용됩니다.
// Lambert (확산) 및 BlinnPhong (스페 큘러) 조명 모델 사용
먼저 PBR 조명 모델이 사용하는 몇 가지 속성을 추가해야합니다.
metallic/스페큘러 맵과 오클루전 맵은 제외하고 있습니다. 주로 샘플링을 처리할 수 있는 좋은 기능이 없기 때문입니다. (LitInput.hlsl에서 복사하지 않는 한 실제 ShaderLibrary가 아닌 Lit 셰이더 URP의 일부입니다.)이 섹션은 이미 꽤 길고 복잡합니다. 주로 어디에서 사용할 기능을 아는 것이기 때문에 실제로 설명 할 수 있는 것은 거의 없습니다. 나중에 LitInput을 예로 사용하여 언제든지 추가 할 수 있습니다.
Properties {
_BaseMap ("Base Texture", 2D) = "white" {}
_BaseColor ("Example Colour", Color) = (0, 0.66, 0.73, 1)
_Smoothness ("Smoothness", Float) = 0.5
[Toggle(_ALPHATEST_ON)] _EnableAlphaTest("Enable Alpha Cutoff", Float) = 0.0
_Cutoff ("Alpha Cutoff", Float) = 0.5
[Toggle(_NORMALMAP)] _EnableBumpMap("Enable Normal/Bump Map", Float) = 0.0
_BumpMap ("Normal/Bump Texture", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[Toggle(_EMISSION)] _EnableEmission("Enable Emission", Float) = 0.0
_EmissionMap ("Emission Texture", 2D) = "white" {}
_EmissionColor ("Emission Colour", Color) = (0, 0, 0, 0)
}
...
// And need to adjust the CBUFFER to include these too
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST; // Texture tiling & offset inspector values
float4 _BaseColor;
float _BumpScale;
float4 _EmissionColor;
float _Smoothness;
float _Cutoff;
CBUFFER_END
일부 multi_compile 및 shader_features 추가 및 Attributes 및 Varyings 구조체에 대한 조정을 포함하여 Unlit 셰이더 코드를 여러 번 변경해야합니다. 조명 계산에 사용하기 위해 메시의 normals 및 탄젠트 데이터가 필요하므로 fragment으로 보냅니다.
Properties 블록의 이러한 TOGGLE 부분을 사용하면 머티리얼 인스펙터에서 shader_feature 키워드를 활성화 / 비활성화 할 수 있습니다. (또는 셰이더용 사용자정의 editor/inspector GUI를 작성하거나 디버그 검사기를 사용할 수 있습니다.)
베이크 된 라이트 맵을 지원하려면 TEXCOORD1 채널에서 전달되는 라이트 맵 UV도 필요합니다.
또한 ShaderLibrary의 SurfaceInput.hlsl을 사용하여 몇 가지를 지원하고 있습니다. SurfaceData 구조체가 PBR에 필요한 데이터와 알베도, 노멀 및 emission맵을 샘플링하는 데 필요한 일부 함수를 보유하는 데 도움이됩니다. (참고 : 해당 구조체가 이동 한 것 같습니다. URPv10에서는 SurfaceData.hlsl.로, SurfaceInput.hlsl에 의해 자동으로 포함됩니다)
// Material Keywords
#pragma shader_feature _NORMALMAP
#pragma shader_feature _ALPHATEST_ON
#pragma shader_feature _ALPHAPREMULTIPLY_ON
#pragma shader_feature _EMISSION
//#pragma shader_feature _METALLICSPECGLOSSMAP
//#pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//#pragma shader_feature _OCCLUSIONMAP
//#pragma shader_feature _SPECULARHIGHLIGHTS_OFF
//#pragma shader_feature _ENVIRONMENTREFLECTIONS_OFF
//#pragma shader_feature _SPECULAR_SETUP
#pragma shader_feature _RECEIVE_SHADOWS_OFF
// URP Keywords
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
#pragma multi_compile _ _MIXED_LIGHTING_SUBTRACTIVE
// Unity defined keywords
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile_fog
// Some added includes, required to use the Lighting functions
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
// And this one for the SurfaceData struct and albedo/normal/emission sampling functions.
// Note : It also defines the _BaseMap, _BumpMap and _EmissionMap textures for us, so we should use these as Shaderlab Properties too.
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
struct Attributes {
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float4 color : COLOR;
float2 uv : TEXCOORD0;
float2 lightmapUV : TEXCOORD1;
};
struct Varyings {
float4 positionCS : SV_POSITION;
float4 color : COLOR;
float2 uv : TEXCOORD0;
DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 1);
// Note this macro is using TEXCOORD1
#ifdef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
float3 positionWS : TEXCOORD2;
#endif
float3 normalWS : TEXCOORD3;
#ifdef _NORMALMAP
float4 tangentWS : TEXCOORD4;
#endif
float3 viewDirWS : TEXCOORD5;
half4 fogFactorAndVertexLight : TEXCOORD6;
// x: fogFactor, yzw: vertex light
#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
float4 shadowCoord : TEXCOORD7;
#endif
};
//TEXTURE2D(_BaseMap);
//SAMPLER(sampler_BaseMap);
// Removed, since SurfaceInput.hlsl now defines the _BaseMap for us
이제 Varyings에는 통과되는 라이트 맵 UV, 법선 및 접선도 포함되어 있지만 조명 계산에 필요한 뷰 방향, 안개, 정점 조명 지원 및 그림자 수신을위한 그림자 좌표도 추가했습니다.
이제 이러한 모든 변경 사항을 처리하기 위해 버텍스 셰이더를 업데이트해야합니다. 이는 주로 사용할 함수를 아는 것입니다.
#if SHADER_LIBRARY_VERSION_MAJOR < 9
// This function was added in URP v9.x.x versions
// If we want to support URP versions before, we need to handle it instead.
// Computes the world space view direction (pointing towards the viewer).
float3 GetWorldSpaceViewDir(float3 positionWS) {
if (unity_OrthoParams.w == 0) {
// Perspective
return _WorldSpaceCameraPos - positionWS;
} else {
// Orthographic
float4x4 viewMat = GetWorldToViewMatrix();
return viewMat[2].xyz;
}
}
#endif
Varyings vert(Attributes IN) {
Varyings OUT;
// Vertex Position
VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);
OUT.positionCS = positionInputs.positionCS;
#ifdef REQUIRES_WORLD_SPACE_POS_INTERPOLATOR
OUT.positionWS = positionInputs.positionWS;
#endif
// UVs & Vertex Colour
OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
OUT.color = IN.color;
// View Direction
OUT.viewDirWS = GetWorldSpaceViewDir(positionInputs.positionWS);
// Normals & Tangents
VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS, IN.tangentOS);
OUT.normalWS = normalInputs.normalWS;
#ifdef _NORMALMAP
real sign = IN.tangentOS.w * GetOddNegativeScale();
OUT.tangentWS = half4(normalInputs.tangentWS.xyz, sign);
#endif
// Vertex Lighting & Fog
half3 vertexLight = VertexLighting(positionInputs.positionWS, normalInputs.normalWS);
half fogFactor = ComputeFogFactor(positionInputs.positionCS.z);
OUT.fogFactorAndVertexLight = half4(fogFactor, vertexLight);
// Baked Lighting & SH (used for Ambient if there is no baked)
OUTPUT_LIGHTMAP_UV(IN.lightmapUV, unity_LightmapST, OUT.lightmapUV);
OUTPUT_SH(OUT.normalWS.xyz, OUT.vertexSH);
// Shadow Coord
#ifdef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR
OUT.shadowCoord = GetShadowCoord(positionInputs);
#endif
return OUT;
}
이제 실제로 UniversalFragmentPBR 함수를 사용하도록 조각을 업데이트 할 수도 있습니다. InputData 구조체 입력이 필요하므로이를 만들고 설정해야합니다. fragment셰이더에서 작업을 수행하는 대신 항목을 구성하는 데 도움이되는 다른 함수를 만들 것입니다.
마찬가지로 모든 Albedo, Metallic, Specular, Smoothness, Occlusion, Emission 및 Alpha 입력을 처리하기 위해 SurfaceData 구조체 (앞에 포함 된 SurfaceInput.hlsl에서 제공)를 사용하고 이를 처리 할 다른 함수를 만듭니다.
InputData InitializeInputData(Varyings IN, half3 normalTS){
InputData inputData = (InputData)0;
#if defined(REQUIRES_WORLD_SPACE_POS_INTERPOLATOR)
inputData.positionWS = IN.positionWS;
#endif
half3 viewDirWS = SafeNormalize(IN.viewDirWS);
#ifdef _NORMALMAP
float sgn = IN.tangentWS.w; // should be either +1 or -1
float3 bitangent = sgn * cross(IN.normalWS.xyz, IN.tangentWS.xyz);
inputData.normalWS = TransformTangentToWorld(normalTS, half3x3(IN.tangentWS.xyz, bitangent.xyz, IN.normalWS.xyz));
#else
inputData.normalWS = IN.normalWS;
#endif
inputData.normalWS = NormalizeNormalPerPixel(inputData.normalWS);
inputData.viewDirectionWS = viewDirWS;
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
inputData.shadowCoord = IN.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS);
#else
inputData.shadowCoord = float4(0, 0, 0, 0);
#endif
inputData.fogCoord = IN.fogFactorAndVertexLight.x;
inputData.vertexLighting = IN.fogFactorAndVertexLight.yzw;
inputData.bakedGI = SAMPLE_GI(IN.lightmapUV, IN.vertexSH, inputData.normalWS);
return inputData;
}
SurfaceData InitializeSurfaceData(Varyings IN){
SurfaceData surfaceData = (SurfaceData)0;
// Note, we can just use SurfaceData surfaceData; here and not set it.
// However we then need to ensure all values in the struct are set before returning.
// By casting 0 to SurfaceData, we automatically set all the contents to 0.
half4 albedoAlpha = SampleAlbedoAlpha(IN.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap));
surfaceData.alpha = Alpha(albedoAlpha.a, _BaseColor, _Cutoff);
surfaceData.albedo = albedoAlpha.rgb * _BaseColor.rgb * IN.color.rgb;
// Not supporting the metallic/specular map or occlusion map
// for an example of that see : https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl
surfaceData.smoothness = _Smoothness;
surfaceData.normalTS = SampleNormal(IN.uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap), _BumpScale);
surfaceData.emission = SampleEmission(IN.uv, _EmissionColor.rgb, TEXTURE2D_ARGS(_EmissionMap, sampler_EmissionMap));
surfaceData.occlusion = 1;
return surfaceData;
}
half4 frag(Varyings IN) : SV_Target {
SurfaceData surfaceData = InitializeSurfaceData(IN);
InputData inputData = InitializeInputData(IN, surfaceData.normalTS);
// In URP v10+ versions we could use this :
// half4 color = UniversalFragmentPBR(inputData, surfaceData);
// But for other versions, we need to use this instead.
// We could also avoid using the SurfaceData struct completely, but it helps to organise things.
half4 color = UniversalFragmentPBR(inputData, surfaceData.albedo, surfaceData.metallic,
surfaceData.specular, surfaceData.smoothness, surfaceData.occlusion,
surfaceData.emission, surfaceData.alpha);
color.rgb = MixFog(color.rgb, inputData.fogCoord);
// color.a = OutputAlpha(color.a);
// Not sure if this is important really. It's implemented as :
// saturate(outputAlpha + _DrawObjectPassData.a);
// Where _DrawObjectPassData.a is 1 for opaque objects and 0 for alpha blended.
// But it was added in URP v8, and versions before just didn't have it.
// And I'm writing thing for v7.3.1 currently
// We could still saturate the alpha to ensure it doesn't go outside the 0-1 range though :
color.a = saturate(color.a);
return color;
}
이것이 바로 PBR 조명입니다. 상당히 많은 코드였습니다! 이것이 어떻게 작동하는지 보는 것이 너무 복잡하지 않기를 바랍니다. 앞서 언급했듯이 주로 어디에서 사용할 기능을 아는 것이므로 설명 할 수있는 내용이 많지 않습니다.
그러나 현재 셰이더는 그림자를받을 수 있지만 ShadowCaster 패스를 포함하지 않으므로 그림자를 드리 우지 않습니다. 이것은 다음 섹션에서 다룰 것입니다.
'Unity > Shader' 카테고리의 다른 글
Summary of Built-in vs URP differences (0) | 2021.01.25 |
---|---|
ShadowCaster & DepthOnly Passes (0) | 2021.01.25 |
Lighting Introduction (0) | 2021.01.22 |
Keywords & Shader Variants (0) | 2021.01.22 |
Fragment Shader (0) | 2021.01.21 |
- Total
- Today
- Yesterday