本代码只适用于平行光。

1、逐顶点漫反射光照

1.1漫反射光照原理

1.2代码实现

在Properties语义块中声明一个漫反射颜色属性

Properties
{
//漫反射参数,用于调整漫反射效果
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}

在SubShader语义块中定义一个Pass语义块。

在Pass的第一行指明光照模式。

	Tags{"LightMode" = "ForwardBase"}

在Pass中,使用CGPROGRAM与ENDCG包围Cg代码片,并告诉Unity顶点着色器与片元着色器的名字。

使用#include包含Unity的内置文件。

	CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//包含内置文件
#include "Lighting.cginc"
ENGCG

接着在CGPROGRAM与ENDCG中定义此前在Properties中定义的属性。

fixed4 _Diffuse;

定义输入与输出结构体

            struct a2v
{
//模型变换
float4 vertex_position : POSITION;
//法线方向(计算)
float3 normal : NORMAL;
}; struct v2f
{
//输出顶点位置
float4 pos : SV_POSITION;
//存储顶点着色器输出的颜色
fixed3 color_in : COLOR;
};

主要计算都集中在顶点着色器。在顶点着色器中,我们有两个目标:把坐标从模型空间转换至裁剪空间中,以及输出漫反射计算之后的color。

坐标变换很简单。代码如下:

                //pos变换
o.pos = UnityObjectToClipPos(v.vertex_position);

计算颜色要复杂一些。根据最上方的公式,我们一共需要四个变量。_Diffuse变量我们已经定义好,光源颜色不需要运算(_LightColor0.rgb可直接得到),接下来需要获取世界坐标下的光源方向世界坐标下的法线方向

fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//法线原本是模型空间下的,需要变换至世界坐标,因此需要mul()
//光源方向本来就是世界坐标下的,因此不需要mul()
//使用normalize()进行归一化

随后可以进行最重要的漫反射运算

防止法线和光源方向点乘的结果为负值,将其截取到0,这可以防止物体被从后面来的光源照亮。

saturate函数是Cg提供的函数,可以将参数截取在[0,1]的范围内。

dot()函数点乘两个向量。

//漫反射部分计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

加上环境光输出

fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
return o;

别忘了在shader最后Fallback。FallBack "Diffuse"

最终写成如下代码:

Shader "Practise/DiffuseVertexShader"
{
Properties
{
//漫反射参数,用于调整漫反射效果
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
//包含内置文件
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag fixed4 _Diffuse; struct a2v
{
//模型变换
float4 vertex_position : POSITION;
//法线方向(计算)
float3 normal : NORMAL;
}; struct v2f
{
//输出顶点位置
float4 pos : SV_POSITION;
//存储顶点着色器输出的颜色
fixed3 color_in : COLOR;
}; v2f vert(a2v v)
{
//两个目标:输出变换后的pos坐标、输出计算后的color
v2f o;
//pos变换
o.pos = UnityObjectToClipPos(v.vertex_position); //color计算
//变量准备
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); //法线原本是模型空间下的,需要变换至世界坐标
//环境光部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射部分计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
//整理输出
o.color_in = ambient + diffuse;
return o;
} fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color_in, 1.0);
} ENDCG
}
}
FallBack "Diffuse"
}

效果如下:

2、逐像素漫反射光照

逐像素光照中,主要的计算过程需要放到片元着色器中。顶点着色器只需要将变换后的位置坐标、变换后的世界空间中的发现坐标传到片元着色器中即可。

在逐顶点漫反射的基础上修改:

//输入顶点着色器
struct a2v{
float3 pos : POSITION;
float3 normal : NORMAL;
};
//顶点着色器输出
struct v2f{
float4 worldpos : SV_POSITION;
fixed3 worldnormal : TEXCOORD0;
};

TEXCOORD0、TEXCOORD1 等语义用于指示任意高精度数据,如纹理坐标和位置。

把计算挪到片元着色器:

            v2f vert(a2v v)
{
// 返回
v2f o;
//变换pos
o.worldpos = UnityObjectToClipPos(v.pos);
//变换法线
o.worldnormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
} fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldnormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}

得到代码:

Shader "Prcatice/DiffusePixelShader"
{
Properties
{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1) }
SubShader
{
Pass{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
//输入顶点着色器
struct a2v{
float3 pos : POSITION;
float3 normal : NORMAL;
};
//顶点着色器输出
struct v2f{
float4 worldpos : SV_POSITION;
fixed3 worldnormal : TEXCOORD0;
}; v2f vert(a2v v)
{
// 返回
v2f o;
//变换pos
o.worldpos = UnityObjectToClipPos(v.pos);
//变换法线
o.worldnormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
} fixed4 frag(v2f i) : SV_TARGET
{
fixed3 worldNormal = normalize(i.worldnormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}

最终效果:

3、对比

逐顶点漫反射可能会在向光面与背光面的之间形成一些锯齿。

(如下图,左侧为逐像素漫反射,右侧为逐顶点漫反射,右侧阴影明显有菱形锯齿)

【Unity Shader学习笔记】Unity光照基础-漫反射光照的更多相关文章

  1. 【Unity Shader学习笔记】Unity光照基础-高光反射

    1.原理 1.1.Phong模型 计算高光反射需要表面法线.视角方向.光源方向.反射方向等. 在这四个矢量中,我们实际上只需要知道其中3个矢量即可,而第4个矢量(反射方向r)可以通过其他信息计算得到: ...

  2. 【Unity Shader学习笔记】Unity光照基础-半兰伯特光照

    在光照无法达到的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面. 使用半兰伯特光照可以解决这个问题. 逐顶点光照技术也被称为兰伯特光照模型.因为它符合兰伯特定律 ...

  3. 【Unity Shader学习笔记】Unity基础纹理-单张纹理

    1 单张纹理 1.1 纹理 使用纹理映射(Texture Mapping)技术,我们把一张图片逐纹素(Texel)地控制模型的颜色. 美术人员建模时,会在建模软件中利用纹理展开技术把纹理映射坐标(Te ...

  4. 【Unity Shader学习笔记】Unity基础纹理-渐变纹理

    纹理可以用来存储任何表面属性. 可以通过使用渐变纹理来实现插画风格的渲染效果. 这项技术是由Valve公司提出的.Valve使用它来渲染游戏中具有插画风格的角色. 我们使用半兰伯特模型计算漫反射. 因 ...

  5. Unity Shader学习笔记-1

    本篇文章是对Unity Shader入门精要的学习笔记,插图大部分来自冯乐乐女神的github 如果有什么说的不正确的请批评指正 目录 渲染流水线 流程图 Shader作用 屏幕映射 三角形遍历 两大 ...

  6. 【Unity Shader学习笔记】Unity基础纹理-法线贴图

    1 高度纹理 使用一张纹理改变物体表面法线,为模型提供更多细节. 有两种主要方法: 1.高度映射:使用一张高度纹理(height map)来模拟表面位移(displacement).得到一个修改后的法 ...

  7. Unity Shader学习笔记 - 用UV动画实现沙滩上的泡沫

    这个泡沫效果来自远古时代的Unity官方海岛Demo, 原效果直接复制3个材质球在js脚本中做UV动画偏移,这里尝试在shader中做动画并且一个pass中完成: // Upgrade NOTE: r ...

  8. Unity Shader 学习笔记(一)

    _MainTex_ST (1)简单来说,TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)主要作用是拿顶点的uv去和材质球的t ...

  9. unity shader学习笔记(1) shader基础结构以及Properties面板

    首先是shader的基础结构: Shader "Custom/Example { Properties//变量属性面板 { } SubShader { Tags { "Render ...

随机推荐

  1. 关于20组---三重奏的meet的评价

    meet这一软件是一款交友软件,新版本完善了以前版本的各种不足,而且能够通过手机号发验证码来创建新账户,功能多样,可以在星球.广场找到自己感兴趣的人,基本满足的交友需求. 但有一点不足之处,就是缺少创 ...

  2. CCF201312-2ISBN号码

    问题描述 每一本正式出版的图书都有一个ISBN号码与之对应,ISBN码包括9位数字.1位识别码和3位分隔符,其规定格式如"x-xxx-xxxxx-x",其中符号"-&qu ...

  3. 解决IDEA中控制台输出乱码

    1. 修改VM Options(2种方法) 第一种,直接修改Tomcat中的 VM Options,这种只对当前项目有效 (1)先点击 Run -> Edit Configurations- 2 ...

  4. 引入css的方式

    ---恢复内容开始--- 引入css的样式及link和@import的区别 有3种引入方式 1.内部样式(写在标签内) 2.内联样式 3.外部样式(link @import) 区别: 1.本质区别:l ...

  5. Java学习笔记(韩顺平教育 b站有课程)

    Java重要特点 面向对象(oop) 健壮性:强类型机制,异常处理,垃圾的自动收集 跨平台性的 (一个编译好的.class可以在多个系统下运行) TEST.java -> TEST.class ...

  6. 一. 为什么要用SpringMVC框架

    以前是怎么做项目的.CoreServlet,起到一个中心处理器作用.所有的请求到服务器,服务器给CoreServlet,在里面处理所有表的增删改查,跳转也在里面做.以前做部门就是 DepServlet ...

  7. 1903021116-吉琛- JAVA第二周作业—Java程序编写

    项目 内容 课程班级博客链接 19级信计班 这个作业要求链接 https://www.cnblogs.com/thelovelybugfly/p/9641367.html 我的课程学习目标 1. 学习 ...

  8. Angular项目构建指南 - 不再为angular构建而犹豫不决(转)

    如果你不知道什么是Angular或者根本没听说过,那么我接下来所说的对你来说毫无益处,不过如果你打算以后会接触Angular或者干脆要涨涨姿势~读下去还是有点用的. Angular和它之前所出现的其余 ...

  9. Revit二次开发之创建风管

      在Revit中,风管用于连接管件,风道末端和机械设备,今天简单尝试了下使用RevitAPI创建风管,现分享下我的方法.   风管从类型上可分为三类:一般风管,软风管和风管占位符:从形状上也分为三类 ...

  10. python学习-Day29

    目录 今日内容详细 反射实际案例 面向对象的双下方法 __ str __ __ del __ __ getattr __ __ setattr __ __ call __ __ enter __ __ ...