各种光照模型的shader实现

news2024/11/28 5:34:39

大家好,我是阿赵。
这里打算给大家介绍一些常用的光照模型的shader实现方法。虽然这些光照模型很多都会在各大引擎内置,一般不需要自己写。但我觉得学习一下,首先对了解渲染原理有帮助,然后对写一些复合效果的shader时,可以比较灵活的控制它的光照效果,也是一件好事情。

一、什么是光照模型

1、模型为什么会有立体感和质感

在这里插入图片描述

首先来看一下这三个球体。
1.第一个球,应该不能算球体,因为看起来它并没有立体感,只能看作是一个2D平面上面的一个圆形
2.第二个球,明显看出来,他是一个立体轮廓,可以肯定他是3D的。
3.第三个球,除了能看出来它是立体的以外,我们还可以强烈的感受到,它的表面很光滑,有一种很硬的感觉。
其实这三个球的网格模型是完全一样的,它们能给我们不同的感官上的认知,这主要是因为光影的问题。
在这里插入图片描述

首先,物体在受光面和背光面,会形成明暗分割线。这个分割线,有可能是很尖锐,也有可能是很柔和。它赋予了模型亮部和暗部的形状,比如球体,他的分割线是一条弧线,代表了物体表面是曲面。而如果是立方体,那么它的明暗分割线都在各条棱上, 表达了物体边缘的转角。
在这里插入图片描述

然后,通过光线在物体表面的反射强度变化,可以模拟到物体表面的光滑度,也就是一般所说的高光。不同的高光形状、高光强度、对比度,可以表达不同表面的不同质感。

2、在引擎里面的光照模型

所谓的光照模型,其实就是通过计算,让物体展现出不同的光照效果的数学模型。
简单的说,一个物体由3种颜色构成:
1.环境光颜色
2.漫反射颜色
3.高光反射颜色
数学模型需要做的事情,就是通过数学方法的计算,求出物体在一个光照空间里面的环境光、漫反射和高光的颜色。
也许很多朋友会觉得,这个所谓的光照模型,好像也没什么存在感?因为平常用游戏引擎,比如Unity3D,新建一个材质球出来,他本身就是有这种立体的效果了。
其实一个shader本身是不会展示光照的效果的,如果你看到模型是立体的,那是因为shader本身调用了引擎内置的光照模型了。比如:
在这里插入图片描述
在这里插入图片描述

如果你建了一个unity的surface类型的shader,会发现可以直接指定它的光照模型类型。
既然游戏引擎里面已经内置了各种的光照模型,为什么我们还需要自己去学光照模型是怎样写的呢?
我们经常听到的一些光照模型,比如上面的Lambert,或者Blinn-Phong,都是前人所发明的一些比较规范的光照模型。假如我们要做风格化的渲染,一般来说就要在这些标准的光照模型上面做一些修改,比如修改它的漫反射色阶,比如修改它的高光形状和渐变方式,等等。所以如果我们能熟悉光照模型的计算方式,会写光照模型,那么我们能实现的效果就多很多了。
说一下这篇文章的内容,由于标准的光照模型公式,在网上随便搜一下就能有,所以我就不写公式了。先直接写个集合了各种常用光照模型的shader贴上来,然后再逐个说一下不同光照模型之间的区别。

二、常用光照模型示例

Shader "azhao/LightModelTest"
{
    Properties
    {
		_mainColor("MainColor", Color) = (1,1,1,1)
		_lightColor("LightColor", Color) = (1,1,1,1)
		_gradationMin("gradationMin", Range(0 , 1)) = 0.5
		_gradationMax("gradationMax", Range(0 , 1)) = 0.6

		_specColor("SpecColor",Color) = (1,1,1,1)
		_shininess("shininess", Range(1 , 100)) = 1
		_tangentVal("TangentVal", Range(0 , 1)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
				float3 normal : NORMAL;
				float3 tangent : TANGENT;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldTangent : TEXCOORD2;
            };

			float4 _mainColor;
			float4 _lightColor;
			float _gradationMin;
			float _gradationMax;

			float4 _specColor;
			float _shininess;
			float _tangentVal;

			//获取Lambert漫反射值
			float GetLambertDiffuse(float3 worldPos, float3 worldNormal)
			{
				float3 lightDir = UnityWorldSpaceLightDir(worldPos);
				float NDotL = saturate(dot(worldNormal, lightDir));
				return NDotL;
			}
			//获取HalfLambert漫反射值
			float GetHalfLambertDiffuse(float3 worldPos, float3 worldNormal)
			{
				float3 lightDir = UnityWorldSpaceLightDir(worldPos);
				float NDotL = saturate(dot(worldNormal, lightDir));
				float halfVal = NDotL * 0.5 + 0.5;
				return halfVal;
			}
			//获取以Lambert为基础的色阶化漫反射值
			float GetLambertGradationDiffuse(float3 worldPos, float3 worldNormal, float min, float max)
			{
				float3 lightDir = UnityWorldSpaceLightDir(worldPos);
				float NDotL = saturate(dot(worldNormal, lightDir));
				float smoothVal = smoothstep(NDotL, min, max);
				return smoothVal;

			}
			//获取以HalfLambert为基础的色阶化漫反射值
			float GetHalfLambertGradationDiffuse(float3 worldPos, float3 worldNormal, float min, float max)
			{
				float3 lightDir = UnityWorldSpaceLightDir(worldPos);
				float NDotL = saturate(dot(worldNormal, lightDir));
				float halfVal = NDotL * 0.5 + 0.5;
				float smoothVal = smoothstep(halfVal, min, max);
				return smoothVal;
			}

			//获取光线反射方向
			float3 GetReflectDir(float3 worldPos, float3 worldNormal)
			{
				float3 worldSpaceLightDir = UnityWorldSpaceLightDir(worldPos);
				float3 reflectDir = normalize(reflect((worldSpaceLightDir * -1.0), worldNormal));
				return reflectDir;
			}

			//获取Phong高光
			float GetPhongSpec(float3 worldPos, float3 worldNormal)
			{
				float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				float3 reflectDir = GetReflectDir(worldPos, worldNormal);
				float specDir = saturate(dot(viewDir, reflectDir));
				float specVal = pow(specDir, _shininess);
				return specVal;
			}

			//获取BlinnPhong高光
			float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal)
			{
				float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
				float specDir = max(dot(normalize(worldNormal), halfDir),0);
				float specVal = pow(specDir, _shininess);
				return specVal;
			}
			//获取Anisortropic各向异性高光
			float GetAnisortropicSpec(float3 worldPos, float3 worldNormal,float3 worldTangent)
			{
				float3 binormal = cross(worldNormal, worldTangent);
				float3 lerpVal = normalize(lerp((worldNormal + binormal), binormal, _tangentVal));

				float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
				float anistropicVal = dot(lerpVal, halfDir);
				float specDir = max(anistropicVal*anistropicVal, 0);
				float specVal = pow(specDir, _shininess);
				return specVal;
			}



            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldTangent = UnityObjectToWorldDir(v.tangent);
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
				//漫反射模式选择
				float diffuseVal = GetLambertDiffuse(i.worldPos,i.worldNormal);//获取Lambert漫反射值
				//float diffuseVal = GetHalfLambertDiffuse(i.worldPos,i.worldNormal);//获取HalfLambert漫反射值
				//float diffuseVal = GetLambertGradationDiffuse(i.worldPos,i.worldNormal,_gradationMin,_gradationMax);//获取以Lambert为基础的色阶化漫反射值
				//float diffuseVal = GetHalfLambertGradationDiffuse(i.worldPos,i.worldNormal,_gradationMin,_gradationMax);//获取以HalfLambert为基础的色阶化漫反射值
				
				//高光模式选择
				//float3 specVal = float3(0, 0, 0);//没有高光
				//float3 specVal = GetPhongSpec(i.worldPos, i.worldNormal);//获取Phong高光
				//float3 specVal = GetBlinnPhongSpec(i.worldPos, i.worldNormal);//获取BlinnPhong高光
				float3 specVal = GetAnisortropicSpec(i.worldPos, i.worldNormal,i.worldTangent);//获取Anisortropic各向异性高光

				//计算漫反射颜色
				float3 diffuseCol = _mainColor * _lightColor*diffuseVal;
				//计算高光颜色
				float3 specCol = specVal * _specColor* _lightColor;


				//最终颜色是环境色+漫反射+高光
				half3 finalCol = UNITY_LIGHTMODEL_AMBIENT + diffuseCol+specCol;
                return float4(finalCol,1);
            }
            ENDCG
        }
    }
}

上面这个shader,通过不同方法的调用,可以组合出多种光照模型结果。各位有兴趣可以把高光和漫反射的部分看一下,修改一下注释,替换不同的光照模型。

1、漫反射

1.Lambert

在这里插入图片描述

最简单而且经典的漫反射光照模型Lambert

//获取Lambert漫反射值
float GetLambertDiffuse(float3 worldPos, float3 worldNormal)
{
	float3 lightDir = UnityWorldSpaceLightDir(worldPos);
float NDotL = saturate(dot(worldNormal, lightDir));
	return NDotL;
}

它的计算方式非常的简单,使用顶点的世界法线方向,和灯光的世界空间方向做一个点乘。一般简称为NDotL。通过点乘的几何含义,可以得出,它其实是求出了灯光方向和法线方向的夹角,用这个夹角的大小来模拟物体的表面是否收到光照,和光照的强度。

2.HalfLambert

在这里插入图片描述

Lambert的光影对比强度很强,模型看起来比较沉重,于是在Lambert的基础上,出现了Half-Lambert

//获取HalfLambert漫反射值
float GetHalfLambertDiffuse(float3 worldPos, float3 worldNormal)
{
float3 lightDir = UnityWorldSpaceLightDir(worldPos);
float NDotL = saturate(dot(worldNormal, lightDir));
float halfVal = NDotL * 0.5 + 0.5;
return halfVal;
}

从计算过程可以看出来,Half-Lambert其实是在Lambert的NDotL的基础上,乘以0.5再加上0.5。这样处理之后,模型的光影对比度就变低了,整个模型看起来也变亮了。

3.色阶化

在这里插入图片描述

所谓的色阶化,是把原来连续的,柔和的曲面光影变化,变成了分段式的亮面和暗面。一般是使用在卡通渲染之类的风格化渲染上面。

//获取以HalfLambert为基础的色阶化漫反射值
float GetHalfLambertGradationDiffuse(float3 worldPos, float3 worldNormal, float min, float max)
{
float3 lightDir = UnityWorldSpaceLightDir(worldPos);
float NDotL = saturate(dot(worldNormal, lightDir));
float halfVal = NDotL * 0.5 + 0.5;
float smoothVal = smoothstep(halfVal, min, max);
return smoothVal;
}

色阶化的方式有很多种,我这里只是随便举了一种而已。比如常见的是通过step函数或者smoothstep函数来把漫反射颜色分段。也可以是把漫反射的颜色作为UV坐标,来读取一张色阶图,实现颜色的分段。

2、高光

1.Phong

在这里插入图片描述

Phong高光

//获取光线反射方向
float3 GetReflectDir(float3 worldPos, float3 worldNormal)
{
float3 worldSpaceLightDir = UnityWorldSpaceLightDir(worldPos);
float3 reflectDir = normalize(reflect((worldSpaceLightDir * -1.0), worldNormal));
return reflectDir;
}

//获取Phong高光
float GetPhongSpec(float3 worldPos, float3 worldNormal)
{
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 reflectDir = GetReflectDir(worldPos, worldNormal);
float specDir = saturate(dot(viewDir, reflectDir));
float specVal = pow(specDir, _shininess);
return specVal;
}

从实现上来看,是通过观察方向和反射方向的点乘来确定高光的方向,一般这个叫做VDotR。
为了控制高光的范围,再对VDotR做一个Power运算。

2.Blinn-Phong

在这里插入图片描述

Phone的高光计算比较真实,但存在2个问题:
因为需要计算光线的反射方向,所以他的计算量比较大,消耗性能比较高
Phone的高光比较集中,通过高光来着色效果不是很理想
于是后来Blinn在Phong的基础上修改出了Blinn-Phong。

//获取BlinnPhong高光
float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal)
{
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
float specDir = max(dot(normalize(worldNormal), halfDir),0);
float specVal = pow(specDir, _shininess);
return specVal;
}

从实现上可以看出,这里已经不需要计算光照反射方向了,取而代之的是一个halfDir。这个中间方向,其实是通过观察方向和灯光方向相加得到的。最后还是通过power来控制高光的范围。
Blinn-Phong的计算量比Phong模型少,然后通过halfDir计算的高光范围,会比纯Phong计算的范围要大,边缘会柔和一点,通过Blinn-Phong来对高光着色,效果会比Phong要柔和一些。

3.Anisortropic各向异性

在这里插入图片描述

通过对比可以看出,各向异性产生的高光,会沿着物体的表面形状发生变化。各向异性一般是用于头发之类非一个平滑面的物体。

//获取Anisortropic各向异性高光
float GetAnisortropicSpec(float3 worldPos, float3 worldNormal,float3 worldTangent)
{
float3 binormal = cross(worldNormal, worldTangent);
float3 lerpVal = normalize(lerp((worldNormal + binormal), binormal, _tangentVal));

float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));
float anistropicVal = dot(lerpVal, halfDir);
float specDir = max(anistropicVal*anistropicVal, 0);
float specVal = pow(specDir, _shininess);
return specVal;
}

我这里用的是一个简化版的各向异性计算方式。里面主要用到了一个binormal 的感念。binormal 是通过法线和切线叉乘得到的,通过叉乘的几何含义,我们可以知道,实际上binormal 是一条垂直法线和切线的向量,它和切线可以定义一个和顶点相切的面。一般我们会用到TBN(Tangent,Binormal,Normal)矩阵进行运算。

三、ASE整理

在这里插入图片描述

我个人挺喜欢使用ASE编辑器去试shader效果,它出效果的效率很高,可视化和节点管理的功能也很好用。比如刚才手写的那段shader,我使用ASE来连线,并且通过成组的注释分块,可以比较清晰的看出整个shader的结果。不过我个人是不会使用ASE生成的Shader代码的,因为很多细节的把控,我个人觉得还是手写的Shader会控制得比较准确一点,可以省略很多没必要的导入和计算。
这里给大家展示的主要是他编辑和管理的便利性,各位有兴趣可以看看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java安全编码规范考试

java安全编码规范考试 整理不易,收点币!! 安全编码规范考试.md 下面对zip文件的安全解压缩描述,错误的是 A.zip文件解压时,可以使用entry.getSize()对解压缩文件进行文件大小判断 B.zip文件解压时&…

Spring Cloud ( Eureka集群的搭建 )

操作步骤: 添加主机映射创建Eureka服务 导入依赖编写启动类编写yml复制服务实例测试一、添加主机映射 以 Windows系统为例,如果要构建集群,需要修改 hosts 文件,为其添加主机名的映射。 打开C:\Windows\System32\drivers\etc\h…

DJ1-4 计算机网络和因特网

目录 一、协议层及其服务模型 ISO/OSI 七层参考模型 TCP/IP 参考模型 1. 网际协议栈(protocol stack) 2. 分层:逻辑通信 3. 协议分层与数据 二、攻击威胁下的网络 1. 植入恶意软件 2. 攻击服务器和网络基础设施 3. 嗅探分组 4. 伪…

为什么机器人操作系统ROS1、ROS2这么难学

AI回复:ROS1、ROS2是机器人操作系统,涉及到很多复杂的概念和技术,需要有一定的编程和机器人知识基础才能学习。此外,ROS1和ROS2的架构和设计也有很大的不同,需要花费一定的时间和精力去学习和适应。但是,一…

DHCP的配置

实验目的熟悉DHCP的应用场景掌握DHCP的配置方法实验拓扑DHCP的配置如图15-2所示: 图15-2:DHCP的配置 实验步骤配置IP地址<Huawei>system-view Enter system view, return user view with Ctrl+Z.

新手入门python实现神经网络,超级简单!

前言&#xff1a; 这篇文章完全是为新手准备的。我们会通过用Python从头实现一个神经网络来理解神经网络的原理。 文章目录神经元1、一个简单的例子2、编码一个神经元把神经元组装成网络1、例子&#xff1a;前馈2、编码神经网络&#xff1a;前馈训练神经网络 第一部分1、损失2、…

C51---串口发送指令,控制LED灯亮灭

1.Code: #include "reg52.h" #include "intrins.h" sfr AUXR 0x8E; sbit D5 P3^7; void UartInit(void) //9600bps11.0592MHz { //PCON & 0x7F; //波特率不倍速 AUXR 0x01; SCON 0x50; //8位数据,可变波…

Spark-RDD 转换算子(双 Value 类型、Key - Value 类型)

双 Value 类型 1、intersection&#xff08;交集&#xff09; 2、union&#xff08;并集&#xff09; 3、subtract&#xff08;差集&#xff09; 4、zip&#xff08;拉链&#xff09; Key - Value 类型 1、partitionBy 2、reduceByKey 3、groupByKey 4、aggregateByK…

FinOps首次超越安全成为企业头等大事|云计算趋势报告

随着云计算在过去十年中的广泛应用&#xff0c;云计算用户所面临的一个持续不变的趋势是&#xff1a;安全一直是用户面临的首要挑战。然而&#xff0c;这种情况正在发生转变。 知名IT软件企业 Flexera 对云计算决策者进行年度调研已经持续12年&#xff0c;而今年安全问题首次…

3.初识Vue

目录 1 vue 浏览器调试工具 1.1 安装 1.2 配置 2 数据驱动视图与双向数据绑定 3 简单使用 3.1 下载 3.2 将信息渲染到DOM上 4 使用vue浏览器调试工具 5 vue指令 1 vue 浏览器调试工具 chrome可能是我浏览器的原因&#xff0c;装上用不了&#xff0c;我们使…

javaWeb核心05-FilterListenerAjax

文章目录Filter&Listener&Ajax1&#xff0c;Filter1.1 Filter概述1.2 Filter快速入门1.2.1 开发步骤1.2.2 代码演示1.3 Filter执行流程1.4 Filter拦截路径配置1.5 过滤器链1.5.1 概述1.5.2 代码演示1.5.3 问题1.6 案例1.6.1 需求1.6.2 分析1.6.3 代码实现1.6.3.1 创建F…

JavaScript Date(日期)对象

日期对象用于处理日期和时间。在线实例返回当日的日期和时间如何使用 Date() 方法获得当日的日期。getFullYear()使用 getFullYear() 获取年份。getTime()getTime() 返回从 1970 年 1 月 1 日至今的毫秒数。setFullYear()如何使用 setFullYear() 设置具体的日期。toUTCString()…

要做一个关于DDD的内部技术分享,记录下用到的资源,学习笔记(未完)

最后更新于2023年3月10日 14:28:08 问题建模》软件分层》具体结构&#xff0c;是层层递进的关系。有了问题建模&#xff0c;才能进行具体的软件分层的讨论&#xff0c;再有了分层&#xff0c;才能讨论在domain里面应该怎么实现具体结构。 1、问题建模&#xff1a;Domain、Mod…

手写模拟SpringMvc源码

MVC框架MVC是一种设计模式&#xff08;设计模式就是日常开发中编写代码的一种好的方法和经验的总结&#xff09;。模型&#xff08;model&#xff09;-视图&#xff08;view&#xff09;-控制器&#xff08;controller&#xff09;&#xff0c;三层架构的设计模式。用于实现前端…

无公网IP快解析实现移动app访问内网应用

随着移动化的发展&#xff0c;国内各大管理软件厂商纷纷推出相应的移动应用服务。移动端为企业提供了不一样的办公方式&#xff0c;从碎片化应用到一体化&#xff0c;同时也为企业办公提供了更高效便捷办公体验。 移动办公和传统pc端在服务器端部署时并没有太大的区别。企业为了…

数据、数据资源及数据资产管理的区别

整理不易&#xff0c;转发请注明出处&#xff0c;请勿直接剽窃&#xff01; 点赞、关注、不迷路&#xff01; 摘要&#xff1a;数据、数据资源、数据资产 数据、数据资源及数据资产的区别 举例 CRM系统建设完成后会有很多数据&#xff0c;这些数据就是原始数据&#xff0c;业务…

线程(操作系统408)

基本概念 我们说引入进程的目的是更好的使用多道程序并发执行&#xff0c;提高资源的利用率和系统吞吐量&#xff1b;而引入线程的目的则是减小程序在并发执行的时候所付出的时间开销&#xff0c;提高操作系统的并发性能。 线程可以理解成"轻量级进程"&#xff0c;…

Request和Response的概述

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;输出优质文章⭐作者主页&#xff1a;︶ㄣ释然⭐如果觉得文章写的不错&#xff0c;欢迎点个关注&#x1f609;有写的不好的地方也欢迎指正&#xff0c;一同进步&#x1f601;Request和Respo…

Flink之Source

Flink 可以从各种来源获取数据&#xff0c;然后构建 DataStream 进行转换处理。一般将数据的输入来源称为数据源&#xff0c;而读取数据的算子就是源算子&#xff08;Source&#xff09;。所以&#xff0c;Source 就是我们整个处理程序的输入端。Flink 代码中通用的添加 Source…

【零基础入门学习Python---Python的五大数据类型之数字类型】

一.Python的五大数据类型之数字类型 在Python中,变量用于存储数据。变量名可以是任何字母、数字和下划线的组合。Python支持多种数据类型,包括数字、字符串、列表、元组和字典。这篇文章我们就来学习一下五大数据类型中的数字类型。 1.1 数字类型 Python 中的数字类型主要…