文章目录

  • BRDF(基本光照模型)
    • 实现漫反射
      • 光线强度的计算
      • 好现在开始写Shader
        • 新建Shader
        • 添加一个Properties语义块
        • 添加SubShader和Pass。
        • 使用CG/HLSL语言来编写顶点/片元着色器
        • 定义a2v和v2f结构体
        • 包含头文件以及声明属性变量
        • 编写顶点着色器
        • 编写片元着色器
          • 将法线向量和光源方向归一化(转为长度为1的单位向量)
          • 计算光线强度并返回颜色
        • 增加对灯光颜色的考虑
          • 包含头文件
          • 修改片元着色器
      • 另一种实现,半兰伯特模型
        • 修改片元着色器

BRDF(基本光照模型)

基本光照模型一共有四种。分别为环境光自发光漫反射高光反射,我们先来实现漫反射

实现漫反射

光线强度的计算

在开始实现漫反射之前,我们需要了解一个概念:辐射度



下图使用兰伯特模型进行漫反射光计算(并且只进行了漫反射光计算),正面对着光线方向的部分最亮,然后逐渐变淡,

兰伯特模型
反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比
(也就是使用我们刚刚的“辐射度”作为漫反射光的强度)

好现在开始写Shader

我们按书里的流程写一个顶点/片元着色器来实现漫反射

新建Shader

首先新建一个Unity Shader,把原有代码全部删除,然后给shader起个名字

Shader "Diffuse-Lambert" {}

添加一个Properties语义块

为Shader添加一个Properties 语义块,声明我们需要的属性。

Properties 语义块是材质和Unity Shader的桥梁,它包含了一系列属性,这些属性会出现在检查器窗口的材质面板中。【candycat】

Shader "Diffuse-Lambert" {Properties {//_Color 为属性标识符,我们会在稍后的Shader编写中使用这个名字//"Color Tint" 为在检查器窗口中显示的属性名称//Color 为属性的类型//等号右边的是属性的默认值,在这里四个1代表为“白色”_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}
}

此时在Unity中随意新建一个材质,并把我们的Diffuse-Lambert着色器拖到材质上,就可以在检查器面板看到我们刚刚声明的属性了

添加SubShader和Pass。

每一个Unity Shader可以定义多个SubShader,但最少要有一个。当Unity需要加载这个UnityShader时,Unity会扫描所有的SubShader语义块,并选出当前显卡能够支持的第一个SubShader运行。【candycat】

Shader "RefShader" {//属性Properties {}//显卡A使用的子着色器SubShader {}//显卡B使用的子着色器SubShader {}
}
Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}SubShader {//如果支持的话,当前显卡会使用这个子着色器//不支持就靠 Fallback了}}

SubShader中定义了一系列Pass以及可选的状态和标签,每个Pass定义了一次完整的渲染流程,所以我们应该尽量使用最小数目的Pass。

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}SubShader {Pass {//在这里会进行一次完整的渲染}}}

在Pass中,我们设置标签LightModeForwardBase(向前渲染),这是为了Unity能够按向前渲染路径的方式为我们正确提供各个光照变量

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}SubShader {Pass {Tags { "LightMode"="ForwardBase" } //向前渲染}}}

使用CG/HLSL语言来编写顶点/片元着色器

我们使用 CGPROGRAM作为CG/HLSL语言的开始符,而ENDCG是结束符。
在CG开始后,我们先来申明vert函数(顶点着色器,逐顶点渲染)和frag函数(片元着色器,逐片元渲染)

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}SubShader {Pass {Tags { "LightMode"="ForwardBase" } //向前渲染CGPROGRAM //CG开始#pragma vertex vert#pragma fragment frag//app to vert,顶点着色器的输入类型,获取当前顶点的信息(由程序给出的)struct a2v {};//vert to frag,片元着色器的输入类型,获取当前片元的信息(由顶点着色器输出的数据再插值得到的)struct v2f {};//顶点着色器,逐顶点运行,输入a2v是当前顶点的信息,输出v2f给片元着色器v2f vert(a2v v) {}//片元着色器,逐片元运行,输入v2f是当前片元的信息,输出颜色fixed4 frag(v2f i) : SV_Target {}ENDCG //CG结束}}}

这个流水线大约是这样子的:

Created with Raphaël 2.2.0开始程序给出当前顶点信息(a2v)顶点着色器储存当前顶点信息(v2f)顶点遍历完了?三角形设置:连接顶点得到三角形网格三角形遍历:检查每个像素,判断是否被一个三角形所覆盖。如果是的话就由该像素生成一个片元,并使用三角网格的3个顶点信息插值得到该片元的信息。划重点:片元其实就是一个像素带着它的信息(我们在v2f中(也就是顶点着色器的返回中)定义需要的信息),这些信息由片元所在的三角形顶点信息插值生成获取当前片元信息(v2f)片元着色器输出当前片元颜色到颜色缓冲区片元遍历完了?结束yesnoyesno

定义a2v和v2f结构体

现在我们的a2v还没有任何字段,也就是说我们的顶点着色器(vert)什么输入都没有,让我们使用语义来给它增加一些数据。

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}SubShader {Pass {Tags { "LightMode"="ForwardBase" } //向前渲染CGPROGRAM //CG开始#pragma vertex vert#pragma fragment fragstruct a2v {//POSITION语义告诉Unity,用模型空间中当前顶点的坐标填充vertex变量float4 vertex : POSITION; //NORMAL语义告诉Unity,用模型空间中当前顶点法的线方向填充normal变量float3 normal : NORMAL;};struct v2f {};v2f vert(a2v v) {}fixed4 frag(v2f i) : SV_Target {}ENDCG //CG结束}}
}

接着给我们的v2f定义结构体,以确保我们在稍后的片元着色器中能够有足够的数据进行运算

我们回顾一下兰伯特模型
反射光线的强度表面法线光源方向之间夹角的余弦值成正比

也就是说,只要得到了片元的表面法线光源方向,我们就能得到该片元漫反射光线的强度

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色}SubShader {Pass {Tags { "LightMode"="ForwardBase" } //向前渲染CGPROGRAM //CG开始#pragma vertex vert#pragma fragment fragstruct a2v {float4 vertex : POSITION; float3 normal : NORMAL;};struct v2f {//SV_POSITION语义告诉Unity,pos里包含了顶点在裁剪空间中的位置信息//这也是顶点着色器最重要的一个工作:将顶点坐标从模型空间转换到裁剪空间float4 pos : SV_POSITION;//TEXCOORD0语义表示worldNormal变量占用了TEXCOORD0插值寄存器//每个插值寄存器可以存储4个浮点值(float)float3 worldNormal : TEXCOORD0; //世界空间下的顶点法线向量float3 worldLightDir : TEXCOORD1; //世界空间下的光源位置};v2f vert(a2v v) {}fixed4 frag(v2f i) : SV_Target {}ENDCG //CG结束}}
}

包含头文件以及声明属性变量

在正式开始计算之前,为了使用一些Unity内置的变量和函数,我们需要包含进内置文件 "UnityCG.cginc"

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) }SubShader {Pass {Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert#pragma fragment frag#include "UnityCG.cginc" //包含内置文件"UnityCG.cginc"struct a2v {float4 vertex : POSITION; float3 normal : NORMAL;};struct v2f {float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldLightDir : TEXCOORD1;};v2f vert(a2v v) {}fixed4 frag(v2f i) : SV_Target {}ENDCG}}
}

而为了可以使用我们在Properties 语义块中定义的属性(那个_Color),我们需要在CG中对属性进行申明

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1) }SubShader {Pass {Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert#pragma fragment frag#include "UnityCG.cginc" fixed4 _Color; //这里的变量名需要和属性的名字完全一致struct a2v {float4 vertex : POSITION; float3 normal : NORMAL;};struct v2f {float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldLightDir : TEXCOORD1;};v2f vert(a2v v) {}fixed4 frag(v2f i) : SV_Target {}ENDCG}}
}

编写顶点着色器

好了,开始写我们的顶点着色器(vert函数)。首先申明一个v2f类型的变量,对结构体中的字段依次赋值,最后将其返回。

v2f vert(a2v v) {//申明返回值v2fv2f o;//这是顶点着色器最重要的一个任务,将顶点坐标从模型空间转换到裁剪空间//UnityObjectToClipPos函数接受一个模型空间的坐标,返回该坐标在裁剪空间的坐标o.pos = UnityObjectToClipPos(v.vertex);//UnityObjectToWorldNormal函数接受一个模型空间的法线向量,将其转换到世界空间中并返回o.worldNormal = UnityObjectToWorldNormal(v.normal);//WorldSpaceLightDir函数接受一个模型空间中的顶点位置,并返回世界空间中从该点到光源的光照方向。未被归一化。(由于是平行光,任何点的光照方向都是一样的,参数填fixed(0)都可以)o.worldLightDir = WorldSpaceLightDir(v.vertex);return o;
}

经过顶点着色器的处理,我们的每个片元中已经包含我们需要的两个信息:法线向量光源方向。现在,让我们在片元着色器中为每个片元计算他们的光线强度

编写片元着色器

将法线向量和光源方向归一化(转为长度为1的单位向量)

由于我们稍后要用点积来求得两向量夹角的cos值,而点积的公式是a·b=|a||b|cosθ(θ为a和b的夹角),很明显,只有当a和b均为单位向量时,点积的结果才会是我们要的两向量的cos值。

fixed4 frag(v2f i) : SV_Target
{//使用normalize()函数对向量进行归一化fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(i.worldLightDir);
}
计算光线强度并返回颜色
fixed4 frag(v2f i) : SV_Target
{fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(i.worldLightDir);//saturate()函数可以将值截取到[0,1],而dot()函数用于计算两向量间的点积//计算出光线强度后,和我们的_Color属性相乘,就能实现一个亮度更改了fixed3 diffuse = saturate(dot(worldNormal, worldLightDir)) * _Color;//返回计算完成的颜色return fixed4(diffuse, 1.0);
}

增加对灯光颜色的考虑

刚刚的计算我们实际上忽略了灯光的颜色,现在我们获取灯光颜色并加入计算。

包含头文件

为了获取灯光颜色,我们需要先在CG中包含头文件 "Lighting.cginc"

Shader "Diffuse-Lambert"{Properties{_Color("Color Tint", Color) = (1, 1, 1, 1)}SubShader {Pass {Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert #pragma fragment frag #include "Lighting.cginc"    //额外包含一个"Lighting.cginc"#include "UnityCG.cginc"fixed4 _Color;struct a2v {float4 vertex : POSITION;float3 normal : NORMAL;};struct v2f{float4 pos : SV_POSITION;float3 worldNormal : TEXCOORD0;float3 worldLightDir : TEXCOORD1;};v2f vert(a2v v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);o.worldLightDir = WorldSpaceLightDir(v.vertex);return o;}fixed4 frag(v2f i) : SV_Target{fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(i.worldLightDir);fixed3 diffuse = saturate(dot(worldNormal, worldLightDir)) * _Color;return fixed4(diffuse, 1.0);}ENDCG}}
}
修改片元着色器
fixed4 frag(v2f i) : SV_Target
{fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(i.worldLightDir);//直接乘上灯光颜色 _LightColor0fixed3 diffuse = saturate(dot(worldNormal, worldLightDir)) * _Color * _LightColor0;return fixed4(diffuse, 1.0);
}

另一种实现,半兰伯特模型

在兰伯特模型中,光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。【candycat】
Valve公司在开发《半条命》时提出了一个新技术,被称为半兰伯特光照模型。在半兰伯特模型中,我们不将片元上法线向量光源方向的cos值截取到[0, 1],而是为这个cos值乘上α倍的缩放然后加上一个β大小的位移,通常,α和β都是0.5。也就是说,半兰伯特模型将法线向量光源方向的cos值从[-1, 1]映射到了[0, 1]。

修改片元着色器

fixed4 frag(v2f i) : SV_Target
{fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(i.worldLightDir);//兰伯特模型//fixed3 diffuse = saturate(dot(worldNormal, worldLightDir)) * _Color * _LightColor0;//半兰伯特模型fixed3 diffuse = (dot(worldNormal, worldLightDir) * 0.5 + 0.5) * _Color* _LightColor0;return fixed4(diffuse, 1.0);
}

UnityShader入门精要笔记1——顶点/片元着色器结构与BRDF(基本光照模型)——实现漫反射相关推荐

  1. 【Unity Shader】学习顶点/片元着色器

    上一篇博客重点放在了Unity Shader的基本结构,分别介绍了它包含的三个语义块,最后简单介绍了Unity Shader的形式:表面着色器.顶点/片元着色器和固定函数着色器. 趁热打铁,今天接着上 ...

  2. 初识顶点/片元着色器

    5.2 一个最简单的顶点/片元着色器 5.2.1 顶点/片元着色器的基本结构 一个 Unity Shader 的基本结构.它包含了 Shader.Properties.SubShader.Fallba ...

  3. UnityShader6:最简单的顶点/片元着色器

    一.顶点/片元着色器基本结构 直接上代码: 这个着色器可以得到蓝色的纯色输出,如果顶点着色器得出了错误的裁剪空间坐标,那么会出现很明显表现错误 Shader "Jaihk662/NewSur ...

  4. java 顶点着色_Shader笔记_002简单顶点/片元着色器扩展

    参考<>第五章 一.如何获取其他模型数据 在001里介绍了通过POSITION获取顶点位置坐标,如果想的到更多的模型数据,比如我们想要得到模型上每个顶点的纹理坐标和法线方向 PS:我们可以 ...

  5. 【Unity Shaders】最简单的顶点/片元着色器3

    微信号:ITComputerGraph 更多精彩内容,关注公众号<IT木子李> Shader "Unity Shaders/Simple Shader3" {SubSh ...

  6. unity无光照着色器(顶点片元着色器)使用TAA抗锯齿后角色移动时模糊(虚影)解决方案

    1.unity系统内置的着色器,移动时没有模糊, 2.一开始想到的是,顶点着色器一般不处理光照,可能是光照与无光照渲染顺序不同所至 2.渲染管线,内置着色器并不和自定义着色器一起提交GPU, 3.TA ...

  7. 【Unity Shader】自定义顶点片元着色器解析

    案例 Shader "Unlit/shader01" {Properties{_MyColor("color", Color) = (1,1,1,1)}SubS ...

  8. Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

    转自冯乐乐的<Unity Shader入门精要> 通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象. 首先,光线从光源中被发射出来. 然后,光线和场景中的一些物体相交 ...

  9. 《UnityShader入门精要》总结(1)理论篇

    紫色:大类概念或简短有力的总结 蓝色:细分概念或重要部分 红色:重要的补充注释 第二章:渲染流程与流程分工 渲染的流程分三个阶段: 应用阶段(开发者控制阶段) 由开发者全权进行管理,控制场景内摄像机位 ...

最新文章

  1. rpm卸载mysql和php_CentOS 6.5 卸载mysql和php
  2. 图解Oracle同义词
  3. [NTU-Machine-learning-note]1 Introduction(4)
  4. 屏蔽Crash 提示框的两种方式
  5. 异步并发利器:实际项目中使用CompletionService提升系统性能的一次实践
  6. Oracle DBA课程系列笔记(4)
  7. Hive 星型模型入门
  8. RPM是RedHat Package Manager(RedHat软件包管理工具)
  9. ASP.NET身份验证机制membership入门——API篇
  10. Oracle回收站使用全攻略
  11. monkey的基本操作命令
  12. 【论文精读1】CSDI: Conditional Score-based Diffusion Models for Probabilistic Time Series Imputation
  13. win10进入bios步骤
  14. 如何在 Titanic Kaggle Challenge 中获得0.8134分
  15. 手把手教你实现一个抽奖系统(Java版)
  16. 专访Polychain创始人Olaf:我们只囤币,从不做空
  17. sublime text3 镜像下载_Sublime Text 3
  18. 在Kmplayer中添加sub字幕过大而挡住视频的解决方法
  19. 乘车码连不上系统服务器,支付宝乘车码无法开通的原因及开通步骤详解
  20. 使用Cisco Packet Tracer练习无线还真不错!

热门文章

  1. 安卓设置EditText回车不换行
  2. Vue学习-异步组件
  3. 超平面是什么?-SVM
  4. iOS(0)- 所思所想
  5. 多功能GPS面积测量仪(测亩仪)技术讲座系列之距离测量的算法研究及实现
  6. Unity3D项目开发一点经验
  7. 网易云音乐App 音频会话管理解析
  8. 帝国CMS资讯自适应HTML5响应式源码,简单而不简约自适应HTML5响应式文章新闻帝国CMS网站模板整站手机...
  9. 前端使用支付宝和微信进行移动支付
  10. Undo Segments