[TUTORIAL] Shader de renderização baseada em física
3 participantes
Página 1 de 1
[TUTORIAL] Shader de renderização baseada em física
Bom dia! Boa tarde! Boa Noite!
Hoje venho trazer um tutorial de HLSL para shader PBR na unity.
E recomendado que você saiba o pelo menos básico de shader e hlsl, já que nesse tutorial não será abordado a criação de shaders.
Eu vou usar uma API própria + Render Pipelines Core para o shader, já que estou usando um SRP (Scriptable Render Pipeline), isso facilita no uso de Global Illumination, nas conversões de espaço, etc.
Mas você pode fazer isso com as ferramentas padrões da unity, porem vai dar um pouco mais de trabalho.
Dito isso vamos começar!
Estrutura Base do shader:
Criei 2 arquivos para deixar mais organizado.
O primeiro e o Shader. (Arquivo .shader)
O segundo serão nossos Pass [Pixel Shader e Vertex Shader]. (Arquivo .hlsl)
PBR.shader
- Código:
Shader "Magic Byte/Standard PBR" {
Properties {
/*Blend options*/
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 1 //One
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 0 //Zero
[Enum(Off, 0, On, 1)] _ZWrite ("Z Write", Float) = 1 //On
/*Blend options*/
[HideInInspector] _MainTex("Lightmap Texture", 2D) = "white" {} // Uso de GI em SRP (Retrire essas propriedades)
[HideInInspector] _Color("Lightmap Color", Color) = (0.5, 0.5, 0.5, 1.0) // Uso de GI em SRP (Retrire essas propriedades)
}
SubShader {
HLSLINCLUDE
#include "../../ShaderLibrary/Common.hlsl" //API de graficos da MB (Retire esse include)
ENDHLSL
Pass {
Tags {
"LightMode" = "MBLit"
}
/*Blend*/
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
/*Blend*/
HLSLPROGRAM
/*Features para SRP*/
#pragma target 3.5
#pragma shader_feature _CLIPPING
#pragma shader_feature _RECEIVE_SHADOWS
#pragma shader_feature _PREMULTIPLY_ALPHA
#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma multi_compile _ _DIRECTIONAL_PCF3 _DIRECTIONAL_PCF5 _DIRECTIONAL_PCF7
#pragma multi_compile _ _CASCADE_BLEND_SOFT _CASCADE_BLEND_DITHER
#pragma multi_compile _ _SHADOW_MASK_ALWAYS _SHADOW_MASK_DISTANCE
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile _ DYNAMICLIGHTMAP_ON
#pragma multi_compile_instancing
/*Features para SRP*/
#pragma vertex PBRPassVertex // <-- VERTEX SHADER
#pragma fragment PBRPassFragment // <-- PIXEL SHADER
#include "PBRPass.hlsl" // <-- Incluir Arquivo com os Pass
ENDHLSL
}
}
}
PBRPass.hlsl
- Código:
#ifndef PBR_PASS_INCLUDED
#define PBR_PASS_INCLUDED
/* API GRAFICA MB */
#include "../../ShaderLibrary/Surface.hlsl"
#include "../../ShaderLibrary/Shadows.hlsl"
#include "../../ShaderLibrary/Light.hlsl"
#include "../../ShaderLibrary/BRDF.hlsl"
#include "../../ShaderLibrary/GI.hlsl"
#include "../../ShaderLibrary/Lighting.hlsl"
#include "../../ShaderLibrary/ClearCoat.hlsl"
/* API GRAFICA MB */
struct Attributes {
float3 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 baseUV : TEXCOORD0;
float2 lightmapUV: TEXCOORD1;
float2 dynamicLightmapUV : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings {
float4 positionCS : SV_POSITION;
float3 positionWS : VAR_POSITION;
float3 normalWS : VAR_NORMAL;
float4 tangentWS : VAR_TANGENT;
float2 baseUV : VAR_BASE_UV;
float2 lightmapUV : TEXCOORD4;
float2 dynamicLightmapUV : TEXCOORD5;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings PBRPassVertex (Attributes input) {
Varyings output;
/* API GRAFICA MB */
output.lightmapUV = input.lightmapUV * unity_LightmapST.xy + unity_LightmapST.zw;
output.dynamicLightmapUV = input.dynamicLightmapUV * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
/* API GRAFICA MB */
/* Render Pipelines Core */
output.positionWS = TransformObjectToWorld(input.positionOS); // MULTIPLICACOES DE MATRIZES PARA A CONVERSAO DE ESPACO
output.positionCS = TransformWorldToHClip(output.positionWS);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.tangentWS = float4(TransformObjectToWorldDir(input.tangentOS.xyz), input.tangentOS.w);
output.baseUV = input.baseUV;
/* Render Pipelines Core */
return output;
}
float4 _Time; //Funcao tempo importada da unity por buffer
float4 PBRPassFragment(Varyings input) : SV_TARGET{
float3 color = (0,0,0);
return float4(color,1);
}
#endif
Buffer _Time
Definições de propriedades:
Antes de começarmos o PBR vamos definir as propriedades do nosso shader.
PBR.shader
- Código:
Properties {
_BaseMap("Texture", 2D) = "white" {} //Mapa de textura
_BaseColor("Color", Color) = (0.5, 0.5, 0.5, 1.0) //Cor de material
_Metallic("Metallic", Range(0, 1)) = 0 //Nivel metalico (0,1)
_Smoothness("Smoothness", Range(0, 1)) = 0.5 //Nivel de "superficie aspera" ou nivel de suavidade
_Occlusion("Occlusion", Range(0, 1)) = 1 //Nivel de microsombras do mapa de oclusao
[NoScaleOffset] _OcclusionMap("Occlusion Map(AO)", 2D) = "white" {} //Mapa de Oclusao
[NoScaleOffset] _SmoothnessMap("Smoothness Map(Specular)", 2D) = "white" {} //Mapa de nivel de suavidade
[NoScaleOffset] _MetalMap("Metallic Map", 2D) = "white" {} //Mapa de nivel metalico
[NoScaleOffset] _EmissionMap("Emission", 2D) = "white" {} //Mapa de emissao
[HDR] _EmissionColor("Emission", Color) = (0.0, 0.0, 0.0, 0.0) //Cor da emissao
[NoScaleOffset] _NormalMap("Normals", 2D) = "bump" {} //Mapa normal
_NormalStrength("Normal Strength", Range(0, 1)) = 1 //Nivel de mapa normal
_Fresnel("Reflectance", Range(0,3)) = 0.0 //Reflectancia do material. (0,3) Para fins artisticos
}
No arquivo "PBRPass.hlsl" vamos definir uma struct para armazenar as propriedades do nosso material (superficie).
PBRPass.hsls
- Código:
struct Surface {
float3 position; //Posicao no espaco de mundo
float3 normal; //Vetor Normal
float3 interpolatedNormal; //Normal de espaco de mundo
float3 viewDirection; //Direcao de visao
float4 tangent; //Vetor de tangentes de superficie
float3 binormal; //Vetor de binormal Produto cruzado -- > cross(vetor normal, vetor tangente) * vetor tangente
float3 color; //Cor da superficie
float alpha; //Nivel de transparencia
float metallic; //Nivel Metalico
float smoothness; //Nivel de suavidade
float occlusion; //Nivel de oclusao
float fresnelStrength; //Nivel de reflectancia
};
Agora vamos adicionar os valores dessas propriedades no nosso struct de superficie:
PBRPass.hlsl
- Código:
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
TEXTURE2D(_NormalMap);
SAMPLER(sampler_NormalMap);
TEXTURE2D(_OcclusionMap);
SAMPLER(sampler_OcclusionMap);
TEXTURE2D(_SmoothnessMap);
SAMPLER(sampler_SmoothnessMap);
TEXTURE2D(_MetalMap);
SAMPLER(sampler_MetalMap);
TEXTURE2D(_EmissionMap);
SAMPLER(sampler_EmissionMap);
float4 _BaseColor;
float _NormalStrength;
float _Metallic;
float _Smoothness;
float _Occlusion;
float4 _EmissionColor;
float _Fresnel;
float4 PBRPassFragment(Varyings input) : SV_TARGET{
float3 color = (0,0,0);
Surface surface;
surface.position = input.positionWS;
float4 Nmap = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.baseUV);
float3 normal = DecodeNormal(Nmap, _NormalStrength);
surface.normal = NormalTangentToWorld(normal, input.normalWS, input.tangentWS); // <---- Transformacao de espaco do Render Pipelines Core
surface.interpolatedNormal = input.normalWS;
surface.viewDirection = normalize(_WorldSpaceCameraPos - input.positionWS); //Normalizar posicao da camera - posicao de objeto
surface.tangent = input.tangentWS;
surface.binormal = cross(NormalTangentToWorld(normal, input.normalWS, input.tangentWS), input.tangentWS.xyz) * input.tangentWS.w;
surface.color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.baseUV) * _BaseColor.rgb;
surface.alpha = _BaseColor.a;
float metallic = _Metallic;
metallic *= pow(abs(SAMPLE_TEXTURE2D(_MetalMap, sampler_MetalMap, input.baseUV).r), 2.2); //Correcao de Gamma do mapa (Fins artisticos)
float smoothness = _Smoothness;
smoothness *= pow(abs(SAMPLE_TEXTURE2D(_SmoothnessMap, sampler_SmoothnessMap, input.baseUV).r), 2.2); //Correcao de Gamma do mapa (Fins artisticos)
//Usamos o nivel de red da textura para definir o nivel de smootheness a partir de uma mascara de smoothness, o mesmo e feito com a mascara(mapa) de metal e de oclusao
float occlusion = _Occlusion;
occlusion *= pow(abs(SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.baseUV).r), 2.2); //Correcao de Gamma do mapa (Fins artisticos)
surface.metallic = metallic;
surface.smoothness = smoothness;
surface.occlusion = occlusion;
surface.fresnelStrength = _Fresnel;
color += SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.baseUV) * _EmissionColor;
return float4(color, surface.alpha);
}
Iluminação Bruta
Como dito acima, estou utilizando um SRP, logo toda a iluminação do meu objeto tem que ser feita por mim. Por esse motivo, o próximo tópico mostra como a iluminação bruta do objeto foi gerada.
Esse tópico serve apenas para mostrar com mais clareza como a luz esta sendo gerada nesse shader, por isso, pode ser ignorado no caso de você estar fazendo um shader com a iluminação da unity.
Por enquanto nosso shader está todo preto, isso acontece pois a única cor adicionada é a emissão.
Então precisamos adicionar a luz da Directional Light, faremos isso a partir dos dados que o pipeline está mandando para a GPU.
c#
Vamos criar uma função para tratar a iluminação de cada Directional Light e também dar suporte a Iluminação Global no PBRPass.
A iluminação bruta será calculada a partir do produto escalar (cosseno) entre o vetor normal e o vetor de direção da luz vezes a atenuação da luz (definida individualmente em cada luz). Depois disso, multiplicamos pela cor da luz, assim as partes escuras permanecem escuras (pois na escala de cor o preto e igual a 0), a as partes claras ganham cor.
Produto escalar é o cosseno para a maioria dos propósitos de Computação Gráfica, já que estamos lidando com vetores de comprimento unitário.
Função Dot:
PBRPass.hlsl
- Código:
float3 Lighting(Surface surface, GI gi) {
ShadowData shadowData = GetShadowData(surface);//Preparar GI
shadowData.shadowMask = gi.shadowMask;
float3 color = (0, 0, 0);
for (int i = 0; i < GetDirectionalLightCount(); i++) {
Light light = GetDirectionalLight(i, surface, shadowData);//Compilar Luz
color += saturate(dot(surface.normal, light.direction) * light.attenuation) * light.color;//Adicionar sombra propria
}
return color;
}
Feito isso, já podemos adicionar a iluminação na cor final do pixel shader:
Arquivo de GI utilizado: GI.hlsl
- Código:
GI gi = GetGI(input.lightmapUV, input.dynamicLightmapUV, surface, (1 - surface.smoothness), 1);
color = Lighting(surface, gi);
Agora já temos nossa primeira iluminação:
Oque é PBR:
PBR ou Physically based rendering (Renderização baseada em física) são técnicas de renderização que tem como objetivo simular aproximações físicas do mundo real na iluminação gerada em computador
Para que um modelo de iluminação seja considerado fisicamente baseado ele deve seguir 3 condições básicas.
1- Conservar sua energia
2- Simulação de microfacetes
3- Modelo de BRDF fisicamente baseado
Nesse tutorial o modelo adotado será um modelo usado na Magic Byte RP parecido com o modelo de BRDF da Disney, que aborda um estilo mais artístico do que físico.
Simulação de Microfacetes:
A teoria de microfacetes nos mostra que toda superfície em uma escala microscópica possui pequenas ranhuras que funcionam como espelhos perfeitamente reflexivos. Em algumas superfícies chamadas de dielétricas, essas ranhuras não são alinhadas espalhando a luz de forma aleatória, assim mantendo uma iluminação difusa.
Já as superfícies metálicas espelham a luz de forma uniforme, criando um brilho especular.
Dielétrica / Metálica
Em resumo, quando mais áspera for a superfície, mais desalinhado as microfissuras estarão.
Modelo de microfacetes:
D(h): Função de distribuição (Modelo usado GGX)
G(n,l,v): Termo geometrico (Modelo usado Smith)
F(n,v): Termo Fresnel (Modelo usado Schlick)
Calcular BRDF
Primeiro temos que criar uma estrutura para armazenar os dados do BRDF, assim como fizemos para a superfície.
PBRPass.hlsl
- Código:
struct BRDF{
float3 diffuse;
float3 specular;
float perceptualRoughness;
float roughness;
};
Agora vamos criar uma função para tratar os dados do BRDF.
- Código:
BRDF GetBRDF(Surface surface) {
BRDF brdf;
brdf.diffuse = surface.color * (1 - surface.metallic);
brdf.specular = lerp(0, surface.color, surface.metallic);
brdf.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);
brdf.roughness = PerceptualRoughnessToRoughness(brdf.perceptualRoughness);
return brdf;
}
E uma função para retornar a simulação de microfacetes (Chamaremos de Iluminação direta).
- Código:
float3 DirectIllumination(Surface surface, BRDF brdf, Light light) {
return brdf.diffuse;
}
Agora vamos adicionar a iluminação direta na nossa função de iluminação bruta
- Código:
float3 Lighting(Surface surface, GI gi) {
ShadowData shadowData = GetShadowData(surface);
shadowData.shadowMask = gi.shadowMask;
BRDF brdf = GetBRDF(surface);
float3 color = (0,0,0);
for (int i = 0; i < GetDirectionalLightCount(); i++) {
Light light = GetDirectionalLight(i, surface, shadowData);//Compilar Luz
color += dot(surface.normal, light.direction) * light.attenuation * light.color * DirectIllumination(surface, brdf, light);//Adicionar sombra propria
}
return color;
}
Com isso já estamos prontos para começar as simulações.
No entanto, quando tentamos usar um material quase 100% metálico o resultado é uma parte difusa completamente preta, oque para fins fisicos não e interessante.
Temos que criar uma função que limite o nível metálico em 0.04 (Nível mínimo definido pela Unity na URP).
- Código:
#define MIN_REFLECTIVITY 0.04 // <--Nivel minimo
float OneMinReflectivity(float metallic) {
float size = 1.0 - MIN_REFLECTIVITY; // <-- Inverter nivel (One Minus)
return size - metallic * size; // <-- Para pequenos niveis retornar inverso da reflexão minima
}
BRDFGetBRDF(Surface surface) {
BRDF brdf;
brdf.diffuse = surface.color * OneMinReflectivity(surface.metallic); //<-- Brilho difuso (com base no nivel metalico)
brdf.specular = lerp(MIN_REFLECTIVITY, surface.color, surface.metallic);
brdf.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);
brdf.roughness = PerceptualRoughnessToRoughness(brdf.perceptualRoughness);
return brdf;
}
Função de Distribuição
Para calcular a termo D vamos usar a função GGX, que utiliza o produto escalar entre vetor mediano, entre o vetor de direção da luz e o vetor normal, e o vetor normal.
NxH = produto cruzado entre o vetor Normal e o vetor Mediano
Nosso hlsl vai ficar assim:
- Código:
//GGX
float GGX(Surface surface, BRDF brdf, Light light) {
float3 h = SafeNormalize(light.direction + surface.viewDirection); //Half Vector (Vetor Mediano)
float NoH = dot(surface.normal, h); //Produto escalar entre vetor normal e vetor mediano
float3 NxH = cross(surface.normal, h);//Produto cruzado entre vetor normal e vetor mediano
float a = NoH * brdf.roughness;
float k = brdf.roughness / (dot(NxH, NxH) + a * a);
float d = k * k * (1.0 / PI);// 1/PI <-- Essa e a funcao lambertiana | A divisão por pi existe para normalizar a luz
return d;
}
float3 DirectIllumination(Surface surface, BRDF brdf, Light light) {
return GGX(surface, brdf, light) + brdf.diffuse;
}
Já temos nosso brilho especular definido.
Termo de Geometria
Para calcular o quanto um microfacete é bloqueado por outro nos usamos o Termo G ou termo de geometria, assim conseguimos calcular a influencia de um microfacete na iluminação do objeto.
No entanto, esse termo é caro em relação a processamento e seu beneficio é pequeno. Por isso, ele não será usado nesse tutorial.
Mas segue métodos de implementação para interessados:
- Código:
float Rough2Max = max(brdf.roughness * brdf.roughness, 2.0e-3);
float k = Rough2Max * 0.5f;
float GSmithL = NoL * (1.0f - k) + k;
float GSmithV = dot(surface.normal, surface.viewDirection) * (1.0f - k) + k;
float G = 0.25f / (GSmithL * GSmithV);
Termo Fresnel
Esse termo será responsável pela distribuição da luz em torno do material de acordo com o nível metálico.
f0 = Nível refletância especular a 0 graus
Vamos criar uma função para processar os dados do fresnel
- Código:
float FresnelSchlick(Surface surface) {
float NoV = dot(surface.normal, surface.viewDirection);
float f0 = pow(1.0 - NoV, 4.0);//Potencia da 4 grau no inverso (OneMinus) do produto escalar
return f0 + NoV * (1.0 - f0) * surface.metallic;
}
Por fim, nosso função de Iluminacao direta fica assim:
- Código:
float3 DirectIllumination(Surface surface, BRDF brdf, Light light) {
return GGX(surface, brdf, light) * FresnelSchlick(surface) + brdf.diffuse;
}
Iluminação Indireta
Criei uma função para processar as informações vindas do arquivo de GI.
- Código:
float3 IndirectIllumination(Surface surface, BRDF brdf, float3 diffuse, float3 specular) {
float3 reflection = specular * lerp(brdf.specular, brdf.fresnel, FresnelBRDF(surface));
reflection /= brdf.roughness * brdf.roughness + 1.0;
return (diffuse * (brdf.diffuse / PI) + reflection) * surface.occlusion;
}
Adicionando luz indireta na iluminacao bruta:
- Código:
float3 Lighting(Surface surface, GI gi) {
ShadowData shadowData = GetShadowData(surface);
shadowData.shadowMask = gi.shadowMask;
BRDF brdf = GetBRDF(surface);
float3 color = IndirectIllumination(surface, brdf, gi.diffuse, gi.specular);
for (int i = 0; i < GetDirectionalLightCount(); i++) {
Light light = GetDirectionalLight(i, surface, shadowData);//Compilar Luz
color += dot(surface.normal, light.direction) * light.attenuation * light.color * DirectIllumination(surface, brdf, light);//Adicionar sombra propria
}
return color;
}
Conservação de Energia
A conservação de energia é um dos principais componentes de um bom BRDF, ele serve para manter os níveis de luz refletida menor do que os níveis de luz que incide no material.
Deixando assim a termodinâmica bem feliz.
Vamos adicionar uma equação simples de conservação de energia ao nosso shader PBR.
- Código:
float3 DirectIllumination(Surface surface, BRDF brdf, Light light) {
float NoL = dot(surface.normal, light.direction);//Produto escalar entre o vetor Normal e o vetor de direcao da luz
float3 energyCompensation = 1.0 + NoL * (1.0 / (1.1 - brdf.roughness) - 1.0); // <----
return GGX(surface, brdf, light) * FresnelSchlick(surface) * energyCompensation + brdf.diffuse;
}
Resultado:
Nível Difuso da Disney
Sem Nivel Difuso da Disney
A disney usa métodos diferentes para determinar a cor difusa de um material.
Por isso, vamos criar uma propriedade para controlar o uso dessa função.
PBR.shader
- Código:
[Enum(Off, 0, On, 1)] _DisneyDiffuse("Disney Diffuse", Float) = 1 //On
Agora no arquivo PBRPass.hlsl, vamos criar a função e adicionar uma variável para essa propriedade:
PBRPass.hlsl
- Código:
float _DisneyDiffuse;
Esse função requer um fresnel de Schlick próprio para transmissão de energia, então vamos coloca-lo tambem:
- Código:
float FresnelTransmission(float f0, float f90, float u)
{
real x = 1.0 - u;
real x2 = x * x;
real x5 = x * x2 * x2;
return (1.0 - f90 * x5) - f0 * (1.0 - x5);
}
- Código:
float disneyDiffuse(float NoV, float NoL, float LoH, float roughness)
{
float energyBias = lerp(0, 0.7, roughness);
float energyFactor = lerp(1.0, 1.0 / 1.51, roughness);
float fd90 = energyBias + 1.0 * LoH * LoH * roughness;
float f0 = 1.0f;
float lightScatter = FresnelTransmission(f0, fd90, NoL);//Controle de transmissao de energia
float viewScatter = FresnelTransmission(f0, fd90, NoV);
return lightScatter * viewScatter * energyFactor;
}
Implementação:
- Código:
float3 DirectIllumination(Surface surface, BRDF brdf, Light light) {
float NoL = dot(surface.normal, light.direction);//Produto escalar entre o vetor Normal e o vetor de direcao da luz
float3 energyCompensation = 1.0 + NoL * (1.0 / (1.1 - brdf.roughness) - 1.0);
float3 h = SafeNormalize(light.direction + surface.viewDirection);
float LoH = dot(light.direction, h);
if(_DisneyDiffuse == 0)
return GGX(surface, brdf, light) * FresnelSchlick(surface) * energyCompensation + brdf.diffuse;
else
return GGX(surface, brdf, light) * FresnelSchlick(surface) * energyCompensation + (1 / PI * disneyDiffuse(dot(surface.normal, surface.viewDirection), NoL, LoH, brdf.roughness) * brdf.diffuse);
}
Com isso também vamos implementar uma opção para mapas de Roughness em vez de Smoothnes
PBR.shader
- Código:
[Enum(Smoothness, 0, Roughness, 1)] _UseRoughness("Smoothness/Roughness", Float) = 0
[NoScaleOffset] _SmoothnessMap("Smoothness Map(Specular)", 2D) = "white" {} //Mapa de nivel de suavidade
PBRPass.hlsl
- Código:
float _UseRoughness;
- Código:
if (_UseRoughness == 0)
surface.smoothness = smoothness;
else
surface.smoothness = PerceptualRoughnessToPerceptualSmoothness(smoothness);
Com nivel difuso da Disney
Shader Completo:
- Código:
Shader "Magic Byte/Standard PBR" {
Properties {
_BaseMap("Texture", 2D) = "white" {} //Mapa de textura
_BaseColor("Color", Color) = (0.5, 0.5, 0.5, 1.0) //Cor de material
[Enum(Off, 0, On, 1)] _DisneyDiffuse("Disney Diffuse", Float) = 1 //On
_Metallic("Metallic", Range(0, 1)) = 0 //Nivel metalico (0,1)
_Smoothness("Smoothness", Range(0, 1)) = 0.5 //Nivel de "superficie aspera" ou nivel de suavidade
_Occlusion("Occlusion", Range(0, 1)) = 1 //Nivel de microsombras do mapa de oclusao
[NoScaleOffset] _OcclusionMap("Occlusion Map(AO)", 2D) = "white" {} //Mapa de Oclusao
[Enum(Smoothness, 0, Roughness, 1)] _UseRoughness("Smoothness/Roughness", Float) = 0
[NoScaleOffset] _SmoothnessMap("Smoothness Map(Specular)", 2D) = "white" {} //Mapa de nivel de suavidade
[NoScaleOffset] _MetalMap("Metallic Map", 2D) = "white" {} //Mapa de nivel metalico
[NoScaleOffset] _EmissionMap("Emission", 2D) = "white" {} //Mapa de emissao
[HDR] _EmissionColor("Emission", Color) = (0.0, 0.0, 0.0, 0.0) //Cor da emissao
[NoScaleOffset] _NormalMap("Normals", 2D) = "bump" {} //Mapa normal
_NormalStrength("Normal Strength", Range(0, 1)) = 1 //Nivel de mapa normal
_Fresnel("Reflectance", Range(0,3)) = 0.0 //Reflectancia do material. (0,3) Para fins artisticos
/*Blend options*/
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 1 //One
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 0 //Zero
[Enum(Off, 0, On, 1)] _ZWrite ("Z Write", Float) = 1 //On
/*Blend options*/
[HideInInspector] _MainTex("Lightmap Texture", 2D) = "white" {} // Uso de GI em SRP (Retrire essas propriedades)
[HideInInspector] _Color("Lightmap Color", Color) = (0.5, 0.5, 0.5, 1.0) // Uso de GI em SRP (Retrire essas propriedades)
}
SubShader {
HLSLINCLUDE
#include "../../ShaderLibrary/Common.hlsl" //API de graficos da MB (Retire esse include)
ENDHLSL
Pass {
Tags {
"LightMode" = "MBLit"
}
/*Blend*/
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
/*Blend*/
HLSLPROGRAM
/*Features para SRP*/
#pragma target 3.5
#pragma shader_feature _CLIPPING
#pragma shader_feature _RECEIVE_SHADOWS
#pragma shader_feature _PREMULTIPLY_ALPHA
#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma multi_compile _ _DIRECTIONAL_PCF3 _DIRECTIONAL_PCF5 _DIRECTIONAL_PCF7
#pragma multi_compile _ _CASCADE_BLEND_SOFT _CASCADE_BLEND_DITHER
#pragma multi_compile _ _SHADOW_MASK_ALWAYS _SHADOW_MASK_DISTANCE
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile _ DYNAMICLIGHTMAP_ON
#pragma multi_compile_instancing
/*Features para SRP*/
#pragma vertex PBRPassVertex // <-- VERTEX SHADER
#pragma fragment PBRPassFragment // <-- PIXEL SHADER
#include "PBRPass.hlsl" // <-- Incluir Arquivo com os Pass
ENDHLSL
}
}
}
- Código:
#ifndef PBR_PASS_INCLUDED
#define PBR_PASS_INCLUDED
/* API GRAFICA MB */
#include "../../ShaderLibrary/Surface.hlsl"
#include "../../ShaderLibrary/Shadows.hlsl"
#include "../../ShaderLibrary/Light.hlsl"
#include "../../ShaderLibrary/BRDF.hlsl"
#include "../../ShaderLibrary/GI.hlsl"
#include "../../ShaderLibrary/Lighting.hlsl"
#include "../../ShaderLibrary/ClearCoat.hlsl"
/* API GRAFICA MB */
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
TEXTURE2D(_NormalMap);
SAMPLER(sampler_NormalMap);
TEXTURE2D(_OcclusionMap);
SAMPLER(sampler_OcclusionMap);
TEXTURE2D(_SmoothnessMap);
SAMPLER(sampler_SmoothnessMap);
TEXTURE2D(_MetalMap);
SAMPLER(sampler_MetalMap);
TEXTURE2D(_EmissionMap);
SAMPLER(sampler_EmissionMap);
float4 _BaseColor;
float _NormalStrength;
float _Metallic;
float _Smoothness;
float _Occlusion;
float4 _EmissionColor;
float _Fresnel;
float _DisneyDiffuse;
float _UseRoughness;
struct BRDFstruct {
float3 diffuse;
float3 specular;
float perceptualRoughness;
float roughness;
};
#define MIN_REFLECTIVITY 0.04
float OneMinReflectivity(float metallic) {
float size = 1.0 - MIN_REFLECTIVITY;
return size - metallic * size;
}
BRDFstruct GetBRDFstruct(Surface surface) {
BRDFstruct brdf;
brdf.diffuse = surface.color * OneMinReflectivity(surface.metallic);
brdf.specular = lerp(MIN_REFLECTIVITY, surface.color, surface.metallic);
brdf.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);
brdf.roughness = PerceptualRoughnessToRoughness(brdf.perceptualRoughness);
return brdf;
}
//GGX
float GGX(Surface surface, BRDFstruct brdf, Light light) {
float3 h = SafeNormalize(light.direction + surface.viewDirection); //Half Vector (Vetor Mediano)
float NoH = dot(surface.normal, h); //Produto escalar entre vetor normal e vetor mediano
float3 NxH = cross(surface.normal, h);//Produto cruzado entre vetor normal e vetor mediano
float a = NoH * brdf.roughness;
float k = brdf.roughness / (dot(NxH, NxH) + a * a);
float d = k * k * (1.0 / PI);// 1/PI <-- Essa e a funcao lambertiana | A divisão por pi existe para normalizar a luz
return d;
}
float FresnelSchlick(Surface surface) {
float NoV = dot(surface.normal, surface.viewDirection);
float f0 = pow(1.0 - NoV, 4.0);//Potencia da 4 grau no inverso (OneMinus) do produto escalar
return f0 + NoV * (1.0 - f0) * surface.metallic;
}
float FresnelTransmission(float f0, float f90, float u)
{
real x = 1.0 - u;
real x2 = x * x;
real x5 = x * x2 * x2;
return (1.0 - f90 * x5) - f0 * (1.0 - x5);
}
float Diffuse(float NoV, float NoL, float LoH, float roughness)
{
float energyBias = lerp(0, 0.7, roughness);
float energyFactor = lerp(1.0, 1.0 / 1.51, roughness);
float fd90 = energyBias + 1.0 * LoH * LoH * roughness;
float f0 = 1.0f;
float lightScatter = FresnelTransmission(f0, fd90, NoL);//Controle de transmissao de energia
float viewScatter = FresnelTransmission(f0, fd90, NoV);
return lightScatter * viewScatter * energyFactor;
}
float3 DirectIllumination(Surface surface, BRDFstruct brdf, Light light) {
float NoL = dot(surface.normal, light.direction);//Produto escalar entre o vetor Normal e o vetor de direcao da luz
float3 energyCompensation = 1.0 + NoL * (1.0 / (1.1 - brdf.roughness) - 1.0);
float3 h = SafeNormalize(light.direction + surface.viewDirection);
float LoH = dot(light.direction, h);
if(_DisneyDiffuse == 0)
return GGX(surface, brdf, light) * FresnelSchlick(surface) * energyCompensation + brdf.diffuse;
else
return GGX(surface, brdf, light) * FresnelSchlick(surface) * energyCompensation + (1 / PI * Diffuse(dot(surface.normal, surface.viewDirection), NoL, LoH, brdf.roughness) * brdf.diffuse);
}
float3 IndirectIllumination(Surface surface, BRDFstruct brdf, float3 diffuse, float3 specular) {
float3 reflection = specular * lerp(brdf.specular, saturate(surface.smoothness + 1.0 - OneMinReflectivity(surface.metallic)), FresnelSchlick(surface));
reflection /= brdf.roughness * brdf.roughness + 1.0;
return (diffuse * (brdf.diffuse / PI) + reflection) * surface.occlusion;
}
struct Attributes {
float3 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 baseUV : TEXCOORD0;
float2 lightmapUV: TEXCOORD1;
float2 dynamicLightmapUV : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings {
float4 positionCS : SV_POSITION;
float3 positionWS : VAR_POSITION;
float3 normalWS : VAR_NORMAL;
float4 tangentWS : VAR_TANGENT;
float2 baseUV : VAR_BASE_UV;
float2 lightmapUV : TEXCOORD4;
float2 dynamicLightmapUV : TEXCOORD5;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
Varyings PBRPassVertex (Attributes input) {
Varyings output;
/* API GRAFICA MB */
output.lightmapUV = input.lightmapUV * unity_LightmapST.xy + unity_LightmapST.zw;
output.dynamicLightmapUV = input.dynamicLightmapUV * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
/* API GRAFICA MB */
/* Render Pipelines Core */
output.positionWS = TransformObjectToWorld(input.positionOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.tangentWS = float4(TransformObjectToWorldDir(input.tangentOS.xyz), input.tangentOS.w);
output.baseUV = input.baseUV;
/* Render Pipelines Core */
return output;
}
float4 _Time; //Funcao tempo importada da unity por buffer
float3 Lighting(Surface surface, GI gi) {
ShadowData shadowData = GetShadowData(surface);
shadowData.shadowMask = gi.shadowMask;
BRDFstruct brdf = GetBRDFstruct(surface);
float3 color = IndirectIllumination(surface, brdf, gi.diffuse, gi.specular);
for (int i = 0; i < GetDirectionalLightCount(); i++) {
Light light = GetDirectionalLight(i, surface, shadowData);//Compilar Luz
color += dot(surface.normal, light.direction) * light.attenuation * light.color;
color *= DirectIllumination(surface, brdf, light);//Adicionar sombra propria
}
return color;
}
float4 PBRPassFragment(Varyings input) : SV_TARGET{
float3 color = (0,0,0);
Surface surface;
surface.position = input.positionWS;
float4 Nmap = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.baseUV);
float3 normal = DecodeNormal(Nmap, _NormalStrength);
surface.normal = NormalTangentToWorld(normal, input.normalWS, input.tangentWS); // <---- Transformacao de espaco do Render Pipelines Core
surface.interpolatedNormal = input.normalWS;
surface.viewDirection = normalize(_WorldSpaceCameraPos - input.positionWS); //Normalizar posicao da camera - posicao de objeto
surface.tangent = input.tangentWS;
surface.binormal = cross(NormalTangentToWorld(normal, input.normalWS, input.tangentWS), input.tangentWS.xyz) * input.tangentWS.w;
surface.color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.baseUV) * _BaseColor.rgb;
surface.alpha = _BaseColor.a;
float metallic = _Metallic;
metallic *= pow(abs(SAMPLE_TEXTURE2D(_MetalMap, sampler_MetalMap, input.baseUV).r), 2.2); //Correcao de Gamma do mapa (Fins artisticos)
float smoothness = _Smoothness;
smoothness *= pow(abs(SAMPLE_TEXTURE2D(_SmoothnessMap, sampler_SmoothnessMap, input.baseUV).r), 2.2); //Correcao de Gamma do mapa (Fins artisticos)
float occlusion = _Occlusion;
occlusion *= pow(abs(SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.baseUV).r), 2.2); //Correcao de Gamma do mapa (Fins artisticos)
surface.metallic = metallic;
if (_UseRoughness == 0)
surface.smoothness = smoothness;
else
surface.smoothness = PerceptualRoughnessToPerceptualSmoothness(smoothness);
surface.occlusion = occlusion;
surface.fresnelStrength = _Fresnel;
GI gi = GetGI(input.lightmapUV, input.dynamicLightmapUV, surface, (1 - surface.smoothness), 1);
color = Lighting(surface, gi);
color += SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, input.baseUV) * _EmissionColor;
return float4(color, surface.alpha);
}
#endif
Na Magic Byte, os shader PBR recebem ainda o Clear Coat e o especular anisotrópico, que não serão abordados nesse tutorial por fugirem um pouco da parte que eu queria mostrar.
Esse foi a tutorial sobre teoria de PBR! Espero que seja útil!
Última edição por Matrirxp em Ter Mar 16, 2021 9:42 pm, editado 5 vez(es)
NKKF- ProgramadorMaster
- PONTOS : 4819
REPUTAÇÃO : 574
Idade : 20
Áreas de atuação : Desenvolvedor na Unity, NodeJS, React, ReactJS, React Native, MongoDB e Firebase.
Respeito as regras :
Re: [TUTORIAL] Shader de renderização baseada em física
Parabéns!!!!!!!!!!!!!!
Caralho eu como uma pesosa "leiga" não consigo entender direito kk
Eu lí o seu tópico e percebi que está bem detalhado e explicado, ficou muito bom mesmo kkk
Fico até triste por não conseguir encontrar um amigo do seu nivel kk
Caralho eu como uma pesosa "leiga" não consigo entender direito kk
Eu lí o seu tópico e percebi que está bem detalhado e explicado, ficou muito bom mesmo kkk
Fico até triste por não conseguir encontrar um amigo do seu nivel kk
Tópicos semelhantes
» Bug Shader / Renderização
» [TUTORIAL] Efeito Outlined (Sublinhado) com shader
» [TUTORIAL] Identificar qual a melhor qualidade gráfica baseada no Hardware
» [TUTORIAL] Como pausar a renderização do Blender
» [TUTORIAL] Guia PBR - Luz e Matéria: A teoria da renderização e sombreamento
» [TUTORIAL] Efeito Outlined (Sublinhado) com shader
» [TUTORIAL] Identificar qual a melhor qualidade gráfica baseada no Hardware
» [TUTORIAL] Como pausar a renderização do Blender
» [TUTORIAL] Guia PBR - Luz e Matéria: A teoria da renderização e sombreamento
Página 1 de 1
Permissões neste sub-fórum
Não podes responder a tópicos