Войти
ПрограммированиеФорумГрафика

Physically Based Rendering - путь новичка

Страницы: 1 2 37 8 Следующая »
#0
11:44, 27 июня 2014

Всем привет!
Предлагаю разобраться (кому это интересно, естественно) в сабже и совместными усилиями набросать шейдер для PBR в Unity3d. Как утверждает один из разработчиков Unity, на PBR переходит вся игровая индустрия и кинематограф.
Спорить с ним не будем, просто давайте разберемся, что это такое и как его сделать самому. Не для того, "чтобы было", а чтобы знать как это работает "под капотом".
Итак, что подразумевается под термином physically based rendering и каковы основные аспекты этого подхода.
Очевидно из названия, что рендеринг при таком подходе и особенно расчет освещения происходят с учетом реальных физических законов.
Основные аспекты:
-Energy Conservation - имеется ввиду, что объект не может отразить больше света, чем получил
-Everything has specular - все материалы отражают свет
-Everything has Fresnel - для всех материалов необходимо учитывать коэффициент Френеля для расчета отражения

Для художников такой подход облегчает настройку материалов, т.к. количество параметров меньше и они интуитивно понятны.
Ну и, конечно, качество картинки заметно растет. Материалы становятся более естественными и лучше вписываются в окружение, т.к. при расчетах используются кубмапы сгенерированные в этом окружении.

Я набросал на Unity обычный surface-шейдер, с Blinn-Phong моделью освещения, на котором мы и будем ставить эксперименты.

Shader "PBR/PBShader"
{
  Properties
  {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _BumpMap ("Normalmap", 2D) = "bump" {}
    _Shininess ("Shininess", Range (0.03, 1)) = 0.078125
  }

  SubShader
  {
    Tags { "RenderType"="Opaque" }
    LOD 200
    
    CGPROGRAM
    #pragma surface surf PhysicallyBased

    sampler2D _MainTex;
    sampler2D _BumpMap;
    half _Shininess;

    struct Input
    {
      float2 uv_MainTex;
      float2 uv_BumpMap;
      float2 uv_GlossMap;
    };

    void surf (Input IN, inout SurfaceOutput o)
    {
      half4 c = tex2D (_MainTex, IN.uv_MainTex);
      o.Albedo = c.rgb;
      o.Alpha = c.a;
      o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
      o.Specular = _Shininess;
      o.Gloss = c.a;
    }

    fixed4 LightingPhysicallyBased(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
    {
      half3 h = normalize(lightDir + viewDir);

      half diff = max(0, dot(s.Normal, lightDir));

      half nh = max(0, dot(s.Normal, h));
      half spec = pow(nh, s.Specular * 128.0) * s.Gloss;

      half4 c;
      c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
      c.a = s.Alpha + _LightColor0.a * spec * atten;
      return c;
    }
    ENDCG
  } 
  FallBack "Diffuse"
}

Все исходники буду выкладывать сюда, пусть люди пользуются)


#1
11:44, 27 июня 2014

Давайте разбираться по пунктам. Что такое Energy conservation и как его вписать в наш шейдер?

#2
11:45, 27 июня 2014

Bonus
> Что такое Energy conservation
<= 1.0 :)

#3
11:49, 27 июня 2014

innuendo
т.е. просто нормализуем diff и spec?

#4
11:51, 27 июня 2014

Bonus
> т.е. просто нормализуем diff и spec?

Относительно чего ?

#5
11:53, 27 июня 2014

innuendo
Их суммы. Получается хрень) уже попробовал

Просто saturate?

#6
11:56, 27 июня 2014

Bonus
> Их суммы

Blinn можно нормализовать через пи - если считать по академической формуле

#7
12:04, 27 июня 2014

Bonus
> Что такое Energy conservation и как его вписать в наш шейдер?
конкретно для блинфонга было много обсуждений, я помню часть из них была неправильная, часть с Пи, часть без, и потом вылезали новые статьи , короче хрен помнит уже правильный, но начиналось всё вроде отсюда
http://www.thetenthplanet.de/archives/255

ну и короче где-то в конце концов ты набредаешь на этот вариант

  vec3 h = normalize( lightDir - viewDir );

  float nh = saturate( dot( h, normal ) );
  float spec = pow( nh, specPow ) * (specPow + 2.0)/8.0;

который вполне себе работает.

>Что такое Energy conservation
в контексте спекуляра это значит, что маленький сфокусированный блик яркий, а широкий по всей поверхности рассеянный - слабо видный. типа потому что кол-во энергии одно приходит и распространяется по разной площади, соотвественно чем шире, тем слабее интенсивность.

---
а может это и у меня неправильная)
вот тут ещё есть
http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
(specPow + 8)/(8*Pi)

#8
12:09, 27 июня 2014

Тут всё написано
http://en.wikipedia.org/wiki/BRDF

#9
12:16, 27 июня 2014

Т.е. диффузный свет никак не связан с сохранением энергии? Это относится только к отраженному свету?

#10
12:19, 27 июня 2014

Bonus
> Т.е. диффузный свет никак не связан с сохранением энергии? Это относится только
> к отраженному свету?

Связан. Смотри BRDF

#11
14:31, 27 июня 2014

Почитал ссылки, которые дали, покрутил шейдер. Вот к чему пришел:
Все считают фактор нормализации по-разному.
Вот 2 более более-менее формализованных:
1. (specPow + 8)/(8*Pi) - тот что давал Mr F
2. ((specPow + 2)*(specPow + 4)) / ((8*Pi) * (pow(2, -specPow / 2) + specPow))

Визуально разницы нет. Оба дают одинаковый блик, который, как и говорил Mr F

маленький сфокусированный блик яркий, а широкий по всей поверхности рассеянный - слабо видный

Еще я домножил spec на diff, чтобы не было блика, там где нет диффузного света.

Shader "PBR/PBShader"
{
  Properties
  {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _BumpMap ("Normalmap", 2D) = "bump" {}
    _Shininess ("Shininess", Range (1, 128)) = 30
  }

  SubShader
  {
    Tags { "RenderType"="Opaque" }
    LOD 200
    
    CGPROGRAM
    #pragma surface surf PhysicallyBased

    sampler2D _MainTex;
    sampler2D _BumpMap;
    half _Shininess;

    struct Input
    {
      float2 uv_MainTex;
      float2 uv_BumpMap;
      float2 uv_GlossMap;
    };

    void surf (Input IN, inout SurfaceOutput o)
    {
      half4 c = tex2D (_MainTex, IN.uv_MainTex);
      o.Albedo = c.rgb;
      o.Alpha = c.a;
      o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
      o.Specular = _Shininess;
      o.Gloss = c.a;
    }

    fixed4 LightingPhysicallyBased(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
    {
      half3 h = normalize(lightDir + viewDir);

      half diff = max(0, dot(s.Normal, lightDir));

      half nh = max(0, dot(s.Normal, h));
      half normalizationFactor = ((s.Specular + 8) / 25.1327412287);//25.1327412287 = Pi * 8
      //half normalizationFactor = ((s.Specular + 2)*(s.Specular + 4)) / (25.1327412287 * (pow(2, -s.Specular / 2) + s.Specular));
      half spec = diff * pow(nh, s.Specular) * s.Gloss * normalizationFactor;

      half4 c;
      c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
      c.a = s.Alpha + _LightColor0.a * spec * atten;
      return c;
    }
    ENDCG
  } 
  FallBack "Diffuse"
}

Такой спекуляр действительно выглядит интересней.

Как нормализовать диффузную составляющую? Или это не нужно делать?

#12
17:07, 27 июня 2014

Разобрался с параметром Roughness.
Он берется из альфы альбедо-текстуры. Это не то же самое, что и Gloss. Gloss теперь не используется.
На основе Roughness вычисляется specPower в пределах от [0.25...2048]
Подсмотрел это в одной из демок

Shader "PBR/PBShader"
{
  Properties
  {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _BumpMap ("Normalmap", 2D) = "bump" {}
    _Roughness ("Roughness", Range (0.0, 1.0)) = 1
  }

  SubShader
  {
    Tags { "RenderType"="Opaque" }
    LOD 200
    
    CGPROGRAM
    #pragma surface surf PhysicallyBased
    #define Pi 3.14159265358979323846

    sampler2D _MainTex;
    sampler2D _BumpMap;
    half _Roughness;

    struct Input
    {
      float2 uv_MainTex;
      float2 uv_BumpMap;
      float2 uv_GlossMap;
    };

    void surf (Input IN, inout SurfaceOutput o)
    {
      half4 c = tex2D (_MainTex, IN.uv_MainTex);
      o.Albedo = c.rgb;
      o.Alpha = c.a;
      o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
      o.Specular = c.a * _Roughness;
      o.Gloss = c.a;
    }

    fixed4 LightingPhysicallyBased(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
    {
      half3 h = normalize(lightDir + viewDir);

      half diff = max(0, dot(s.Normal, lightDir));

      half nh = max(0, dot(s.Normal, h));

      //приводим specPower в промежуток [0.25...2048]
      float specPower = exp2(10 * s.Specular + 1) - 1.75;

      half normalizationFactor = ((specPower + 8) / 25.1327412287);//25.1327412287 = Pi * 8

      half spec = diff * pow(nh, specPower) * normalizationFactor;

      half4 c;
      c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
      c.a = s.Alpha + _LightColor0.a * spec * atten;
      return c;
    }
    ENDCG
  } 
  FallBack "Diffuse"
}
Вот так работает шейдер сейчас
Изображение
Справа стандартный BumpSpecular, слева наш.

#13
18:04, 27 июня 2014

Bonus
Что-то на energy conservation не похоже.

#14
18:09, 27 июня 2014

Это выкрученный на полную Roughness слева и Shininess справа.
Если в нашем шейдере уменьшать Roughness, то блик будет расширяться, но при этом терять интенсивность.
Если в стандартном шейдере уменьшать Shininess, то блик будет расширяться, но интенсивность будет постоянной.

Я так понимаю это и есть сохранение энергии. На статической картинке не знаю как ее показать.

Страницы: 1 2 37 8 Следующая »
ПрограммированиеФорумГрафика

Тема в архиве.