阴影
计算光照影响,需要重要的参数
法线和光以及材质属性。 法线长度?
基础修改
- 添加 Pass'Tag
Tags {
"LightMode" = "CustomLit"
}
- 添加绘制代码
litShaderTagId = new ShaderTagId("CustomLit");
//...
drawingSettings.SetShaderPassName(1, litShaderTagId);
- 着色器结构添加法线属性
struct a2v
{
float3 position : POSITION;
float3 normal : NORMAL;
//...
UNITY_VERTEX_INPUT_INSTANCE_ID
}
struct v2f
{
float3 position : SV_POSITION;
float3 normal : VAR_NORMAL;
//...
UNITY_VERTEX_INPUT_INSTANCE_ID
}
v2f vert(a2v input)
{
//...
output.normal = TransformObjectToWorldNormal(input.normal);
//...
}
float4 frag(v2f input):SV_Target
{
float4 base = (1.0, 1.0, 1.0, 1.0);
// 使用法线长度进行显示
base.rgb = abs(length(input.normal) - 1.0) * 10.0;
return base;
}
光照计算
简单使用
- 定义光照计算属性结构
struct Surface {
float3 normal;
float3 color;
float alpha;
};
- frag 进行属性填充和计算
//...
Surface surface;
surface.normal = normalize(input.normal);
surface.color = base.rgb;
surface.alpha = base.a;
return float4(surface.color, surface.alpha);
//...
- 计算光照
float3 color = GetLighting(surface);
return float4(color, surface.alpha);
// 基础版本 使用法线y分量 作为影响值。
float3 GetLighting (Surface surface) {
return surface.normal.y * surface.color;
}
光照计算
- 定义光源属性
// 基础版本 颜色和方向。
struct Light {
float3 color;
float3 direction;
};
- 光照函数
** saturate 截止在0-1 范围, 去掉负值,也就是背面影响。**
// 使用光照在法线的分量 和 光照颜色 乘积作为影响因子
float3 IncomingLight (Surface surface, Light light) {
return saturate(dot(surface.normal, light.direction)) * light.color;
}
// 融合光照影响和材质颜色属性。
float3 GetLighting (Surface surface, Light light) {
return IncomingLight(surface, light) * surface.color;
}
// todo, 这个省却光照, 使用太阳光作为默认参数。
float3 GetLighting (Surface surface) {
return GetLighting(surface, GetDirectionalLight());
}
- 添加直线光数据给GPU
Shader部分定义。
CBUFFER_START(_CustomLight)
float3 _DirectionalLightColor;
float3 _DirectionalLightDirection;
CBUFFER_END
Light GetDirectionalLight () {
Light light;
light.color = _DirectionalLightColor;
light.direction = _DirectionalLightDirection;
return light;
}
C# 部分进行传输数据。 使用commandbuffer 进行设置
sing UnityEngine;
using UnityEngine.Rendering;
public class Lighting {
// Shader 属性
static int
dirLightColorId = Shader.PropertyToID("_DirectionalLightColor"),
dirLightDirectionId = Shader.PropertyToID("_DirectionalLightDirection");
const string bufferName = "Lighting";
CommandBuffer buffer = new CommandBuffer {
name = bufferName
};
public void Setup (ScriptableRenderContext context) {
buffer.BeginSample(bufferName);
SetupDirectionalLight();
buffer.EndSample(bufferName);
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
}
void SetupDirectionalLight () {
Light light = RenderSettings.sun; // **默认的太阳光,因此场景中必须至少有一个。也就是说光照设置中的`Sun Source`值不能为空。**
buffer.SetGlobalVector(dirLightColorId, light.color.linear * light.intensity); //**.linear 这个很重要**
buffer.SetGlobalVector(dirLightDirectionId, -light.transform.forward);
}
}
在Render 中进行调用
Lighting lighting = new Lighting();
//...
lighting.Setup(context);
改进完善版本, 使用剔除的可见光 和多光源
- 改进1,使用剔除的可见光,而不是直接使用 Sun。
- 改进2,传递光原数组 、颜色数组 和光源数量
- 改进3,传递数组,Shader也跟着改,并且混合多光源影响
- 改进4,使用Linear
光照绘制。
GraphicsSettings.lightsUseLinearIntensity = true;
同样问题, 至少有一个直线光。
// 渲染管线设置
`GraphicsSettings.lightsUseLinearIntensity = true;`
// Render调用修改
lighting.Setup(context, cullingResults);
// 光源设置数据修改
const int maxDirLightCount = 4;
// 对应Shader 属性
static int
dirLightCountId = Shader.PropertyToID("_DirectionalLightCount"),
dirLightColorsId = Shader.PropertyToID("_DirectionalLightColors"),
dirLightDirectionsId = Shader.PropertyToID("_DirectionalLightDirections");
static Vector4[]
dirLightColors = new Vector4[maxDirLightCount],
dirLightDirections = new Vector4[maxDirLightCount];
//
CullingResults cullingResults;
public void Setup (
ScriptableRenderContext context, CullingResults cullingResults
) {
this.cullingResults = cullingResults;
buffer.BeginSample(bufferName);
//SetupDirectionalLight();
// 修改为使用多光源
SetupLights();
//…
}
//...
//
void SetupDirectionalLight (int index, VisibleLight visibleLight) {
dirLightColors[index] = visibleLight.finalColor;
dirLightDirections[index] = -visibleLight.localToWorldMatrix.GetColumn(2);
}
void SetupLights () {
NativeArray<VisibleLight> visibleLights = cullingResults.visibleLights;
for (int i = 0; i < visibleLights.Length; i++) {
VisibleLight visibleLight = visibleLights[i];
// 只处理直线光影响 和最大数量支持以内的直线光。
if (visibleLight.lightType == LightType.Directional) {
** 值类型拷贝会引起内存增长,因此改为ref形式 **
SetupDirectionalLight(dirLightCount++, ref visibleLight);
if (dirLightCount >= maxDirLightCount) {
break;
}
}
}
buffer.SetGlobalInt(dirLightCountId, visibleLights.Length);
buffer.SetGlobalVectorArray(dirLightColorsId, dirLightColors);
buffer.SetGlobalVectorArray(dirLightDirectionsId, dirLightDirections);
}
Shader部分修改
#define MAX_DIRECTIONAL_LIGHT_COUNT 4
CBUFFER_START(_CustomLight)
//float4 _DirectionalLightColor;
//float4 _DirectionalLightDirection;
int _DirectionalLightCount;
float4 _DirectionalLightColors[MAX_DIRECTIONAL_LIGHT_COUNT];
float4 _DirectionalLightDirections[MAX_DIRECTIONAL_LIGHT_COUNT];
CBUFFER_END
int GetDirectionalLightCount () {
return _DirectionalLightCount;
}
Light GetDirectionalLight (int index) {
Light light;
light.color = _DirectionalLightColors[index].rgb;
light.direction = _DirectionalLightDirections[index].xyz;
return light;
}
// 使用 多光源混合
float3 GetLighting (Surface surface) {
float3 color = 0.0;
for (int i = 0; i < GetDirectionalLightCount(); i++) {
color += GetLighting(surface, GetDirectionalLight(i));
}
return color;
}
由于 GetDirectionalLightCount(); 作为循环长度。
而老版本OpenGL ES 2.0和WebGL 1.0图形api在默认情况下不能处理这样的循环,
以及线性光照。所以使用
#pragma target 3.5指令将着色器的目标级别提升到3.5。
HLSLPROGRAM
#pragma target 3.5
//…
ENDHLSL
BRDF
前面使用简化的光照模型只能处理理想的细节,使用
BRDF(双向反射分布函数) 可以处理更多样化的细节表面。 有很多种BRDF
的实现,性能上有所不同, 可以使用 Universal RP版本的 BRDF
实现。
理想的光线反射按照镜面反射。但是如果表面有更多的细节,不够平坦,那么就可能会向四周进行反射。因此无论相机在哪里,从表面接收到的漫射光的量是相同的。但这意味着我们观测到的光能远小于到达表面碎片的光能。
- 添加细节控制属性
_Metallic ("Metallic", Range(0, 1)) = 0 // 金属度
_Smoothness ("Smoothness", Range(0, 1)) = 0.5 //光滑度
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseMap_ST)
UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_DEFINE_INSTANCED_PROP(float, _Cutoff)
// 添加至 Instanced 属性
UNITY_DEFINE_INSTANCED_PROP(float, _Metallic)
UNITY_DEFINE_INSTANCED_PROP(float, _Smoothness)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)
struct Surface {
float3 normal;
float3 color;
float alpha;
// 细节表面添加 金属度和光滑度 属性。
float metallic;
float smoothness;
};
float4 frag(v2f input): SV_Target
{
Surface surface;
surface.normal = normalize(input.normalWS);
surface.color = base.rgb;
surface.alpha = base.a;
// 赋值添加金属度和光滑度
surface.metallic = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Metallic);
surface.smoothness =
UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Smoothness);
// ...
}
- 测试代码,进行金属度和光滑度赋值
static int
baseColorId = Shader.PropertyToID("_BaseColor"),
cutoffId = Shader.PropertyToID("_Cutoff"),
metallicId = Shader.PropertyToID("_Metallic"),
smoothnessId = Shader.PropertyToID("_Smoothness");
//...
block.SetFloat(metallicId, metallic);
block.SetFloat(smoothnessId, smoothness);
GetComponent<Renderer>().SetPropertyBlock(block);
- 添加BRDF 结构,结构化 BRDF影响值
struct BRDF {
float3 diffuse;
float3 specular;
float roughness;
};
- 添加 BRDF 光照函数
//简单使用, 只用 漫反射进行影响。
BRDF GetBRDF (Surface surface) {
BRDF brdf;
brdf.diffuse = surface.color;
brdf.specular = 0.0;
brdf.roughness = 1.0;
return brdf;
}
float3 GetLighting (Surface surface, BRDF brdf, Light light) {
return IncomingLight(surface, light) * brdf.diffuse;
}
float3 GetLighting (Surface surface, BRDF brdf) {
float3 color = 0.0;
for (int i = 0; i < GetDirectionalLightCount(); i++) {
color += GetLighting(surface, brdf, GetDirectionalLight(i));
}
return color;
}
// Shader中使用。
BRDF brdf = GetBRDF(surface);
float3 color = GetLighting(surface, brdf);
- 使用反射率 BRDF
漫反射 和 高光
#define MIN_REFLECTIVITY 0.04
float OneMinusReflectivity (float metallic) {
float range = 1.0 - MIN_REFLECTIVITY;
return range - metallic * range;
}
//...
float oneMinusReflectivity = OneMinusReflectivity(surface.metallic);
brdf.diffuse = surface.color * oneMinusReflectivity;
//brdf.specular = surface.color - brdf.diffuse; // 简单使用进出相等。
// 使用金属度 进行差值 color
brdf.specular = lerp(MIN_REFLECTIVITY, surface.color, surface.metallic);
//...
粗糙度
// 使用 Universal RP 粗糙度转换
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
// 光滑度 转换为粗糙度 1 - 光滑度。
float perceptualRoughness =
PerceptualSmoothnessToPerceptualRoughness(surface.smoothness);
// 粗糙度平方
brdf.roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
高光部分需要使用到 视线方向,原理是光照在 视线的分量。
// 相机位置,后面会在cs中进行设置
float3 _WorldSpaceCameraPos;
//表面细节添加 视线属性
struct Surface {
float3 normal;
// 视线
float3 viewDirection;
float3 color;
float alpha;
float metallic;
float smoothness;
};
struct v2f
{
float3 position : SV_POSITION;
float3 positionWorld : VAR_POSITION; //储存worldPos
//...
}
v2f vert(a2v input)
{
//...
output.positionWorld = TransformObjectToWorld(input.position);
output.position = TransformWorldToHClip(output.positionWorld);
//...
}
float4 frag(v2f input): SV_Target
{
surface.viewDirection = normalize(_WorldSpaceCameraPos - input.positionWorld);
}
高光强度计算, 使用简化版本的 CookTorrance BRDF,公式:
BRDF TODO
ShaderGUI
通过在 Shader 最后中指定 自定义的ShaderGUI
Shader "ShaderName" {
//...
CustomEditor "CustomShaderGUI"
}
C#
public class CustomShaderGUI : ShaderGUI
{
public override void OnGUI (MaterialEditor materialEditor, MaterialProperty[] properties)
{
//base.OnGUI (materialEditor, properties);
Material targetMat = materialEditor.target as Material;
//...
}
}