如果前面几个系列文章的内容过于冗长缺乏趣味着实见谅,由于时间原因前面的混合部分还没有写完,等以后再补充,现在开始关于反射的内容了。
折射与反射
在物理世界中,光的反射与折射往往是同时存在的,光源由真空或者空气中射入一种材料,光在进入这种材料的同时就发生了折射,折射的程度与各个介质的折射率有关,使光的传播路线偏离原来的路线;
继而如果光在通过不同传播介质的表面时,会像乒乓球一样弹回来,我们人眼能够看到东西,都是因为东西会反射光源,如果一种物质无法反射光,或者没有光源,我们就看不到东西。同样对于不同的材料,光的通过性越高,反射越不明显,比如玻璃(但是玻璃同样会有一定的反射,不然我们是看不到玻璃的,实际上我们看得到);光的通过性越低,反射越明显,比如水面,我们可以看到水中的鱼(光从水射到鱼的皮肤,鱼的皮肤发生了漫反射),也可以看到自己的倒影(水面发生了镜面反射)。

镜面反射与漫反射
那么镜面反射与漫反射的区别就是光碰到的物体表面被弹回来的过程中,表面的平整程度,越平越接近镜面反射,越粗糙就是漫反射。我们现实生活中看到的绝大部分东西的表面都是粗糙的,所以都是漫反射。

在讨论漫反射之前一定要回过头来复习一下我们的物理科学中的镜面反射与折射,因为漫反射可以拆分为无数个微小的镜面反射(就像圆形可以看做无数条边的正多边形),毕竟游戏世界也是为了模拟物理世界,游戏引擎必须要符合物理规律。画个图回忆一下:

上面这张图我画的不是很好,我需要说明的入射光线与法平面的夹角是入射角,反射光线与法平面的夹角是反射角,入射角永远等于反射角,图没画好。

同理对于凹凸不平的介质交汇面,我们可以无限细分为无数个法平面,对于每一个表面的点的反射情况,我们都可以沿着当前点做一个切面与物体表面在该点相切,得到一个法平面。所以镜面反射的规律可以直接适用到漫反射。示意一下:

我们对于介质B的表面的某一点的反射情况进行分析的时候就在这个点做一个切面与表面相切于这个点,同样得到了这个位置的法平面、法线、入射角、反射角

好了复习到这里了,在前面的系列中我们已经解除到了法线与法平面,我们将我们的三维物体看到上图的介质B,那么黑色曲线部分为它的部分表面,则这个表面上的每一个点的法线的朝向都不相同,因为每一个点做切面切与这个表面得到的平面不一定平行,所以在CG中mesh信息中传递过来的每一个顶点都有一个normal值,即法向量。

法向量就是我们的法线中切点往介质A的方向上的单位向量。

反射过程
好了 接下来我们将前文提到的入射光线、法线 替换为入射向量L(表面到光源的方向为正方向)、法向量N。
那么我们的物体表面发生漫反射时,漫反射的量即是由入射向量I与每一个顶点位置的法向量N确定的:
diffuse=L·N
这个过程是向量I与N进行点乘,其过程为:
diffuse=|L|*|N|*cos∠(L,N)

由于我们直接讨论入射向量与法向量的单位向量,所以两个绝对值为1,那么漫反射duffuse=cos∠(L,N)

由此我们可以得到单位向量的入射光的漫反射强度diffuse的值域为:[-1,1]
由余弦曲线可以得知,diffuse在夹角为0~90°为正,90°~270°为负,270~360°为正

对于cos∠(L,N)超过90°的情况,我们认为入射光已经在介质表面的另外一面了,这种情况我们不考虑,一般来说漫反射都是在外表面进行的。

所以我们的diffuse当夹角超过90°时便认为反射量为0,所以我们对该式子修改为:

diffuse=max(0,cos∠(L,N))

即对入射向量与法向量的夹角的余弦 与0 取最大值,保证了反射量不会出现负数。

接下来让我们从物理和数学世界回到CG世界中

用CG编写漫反射Shader
在编写我们的第一个单光源漫反射shader之前,先说明一下我们需要的几个参数的来源:
1.光源的位置由unity的内置uniform参数 _WorldSpaceLightPos0给出,由此我们可以计算出每个顶点的入射向量
2.光源的颜色由uniform参数_LightColor0给出
3.法向量直接通过顶点着色器的输入参数中的带语义NORMAL来获得
有了入射向量、法向量、光源颜色,我们就能在顶点着色器中计算出每个顶点位置的反射量,并与光源颜色和材料颜色相乘得到最终表面实际着色的颜色。
用户自定义的参数:
1.材料颜色,通过shader的property定义

Shader "Custom/CustomDiffuse" {
Properties {
//材料颜色默认为黑色,可在inspector中调节
_Color ("Material Color", Color) = (1,1,1,1)
}
SubShader {
//LightMode我们在后面会继续讨论
Tags { "LightMode" = "ForwardBase" }

Pass{
CGPROGRAM
// Upgrade NOTE: excluded shader from OpenGL ES 2.0 because it does not contain a surface program or both vertex and fragment programs.
//#pragma exclude_renderers gles
//定义顶点着色器与片段着色器入口
#pragma vertex vert
#pragma fragment frag
//获取property中定义的材料颜色
uniform float4 _Color;

// 光源的位置或者方向
//uniform float4 _WorldSpaceLightPos0;

// 光源的颜色 (from "Lighting.cginc")
uniform float4 _LightColor0;

//定义顶点着色器的输入参数结构体
//我们只需要每个顶点的位置与对应的法向量
struct vertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//定义顶点着色的输出结构体/片段着色的输入结构体
//已经计算好的颜色
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : COLOR;
};

//顶点着色器
vertexOutput vert (vertexInput input) {
vertexOutput output;
//对象坐标系到世界坐标系的变换矩阵
//_Object2World与_World2Object均为unity提供的内置uniform参数
float4x4 modelMatrix = _Object2World;
//世界坐标系到对象坐标系的变换矩阵
float4x4 modelMatrixInverse = _World2Object;

//计算对象坐标系中的顶点法向量的单位向量
//将mesh传递过来的顶点法向量与模型-->对象坐标系矩阵相乘得到对象坐标系中的法向量
//然后单位化
float3 normalDirection = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));

//计算入射向量的单位向量
float3 lightDirection = normalize(float3(_WorldSpaceLightPos0));

//计算反射后的颜色
//先将光源颜色与材料颜色向量相乘
//再乘以上文提到的max(0,cos∠(N,L))
float3 diffuseReflection=float3(_LightColor0) * float3(_Color)* max(0.0, dot(normalDirection, lightDirection));

//上面计算的是RGB颜色,差个A,补充一维就可以传递给片段着色器了
output.col=float4(diffuseReflection,1.0);

//国际惯例,顶点变化三步曲,这个例子中可写可不写
output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

return output;
}

//片段着色器,老规矩,把顶点着色器的输出参数作为片段着色器的输入参数
float4 frag(vertexOutput input): COLOR
{
return input.col;

}

ENDCG
}
}
}

至此我们创建一个material 然后选择我们新建的这个shader,在inspector中选择一个颜色,然后再场景中布置一个唯一光源,将这个material给一个物体。

我把这个material给了奥迪A6L的引擎盖,光源在正前方,可以直观的看一下,引擎盖使用的shader是我们刚刚编写的漫反射shader,车身其余部分为镜面反射shader:

[转]解读Unity中的CG编写Shader系列7——漫反射的更多相关文章

  1. 解读Unity中的CG编写Shader系列八(镜面反射)

    转自http://www.itnose.net/detail/6117378.html 讨论完漫反射之后,接下来肯定就是镜面反射了 在开始镜面反射shader的coding之前,要扩充一下前面提到的知 ...

  2. [转]解读Unity中的CG编写Shader系列9——镜面反射

    讨论完漫反射之后,接下来肯定就是镜面反射了在开始镜面反射shader的coding之前,要扩充一下前面提到的知识,加深理解镜面反射与漫反射的区别.注:这篇文章实现的镜面反射是逐顶点着色(per-ver ...

  3. 解读Unity中的CG编写Shader系列七(不透明度与混合)

    转自http://www.itnose.net/detail/6098539.html 1.不透明度 当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段 ...

  4. 解读Unity中的CG编写Shader系列三

    转自http://www.itnose.net/detail/6096068.html 在上一个例子中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上.这篇文章 ...

  5. [转]解读Unity中的CG编写Shader系列6——不透明度与混合

    1.不透明度当我们要将两个半透的纹理贴图到一个材质球上的时候就遇到混合的问题,由于前面的知识我们已经知道了片段着色器以及后面的环节的主要工作是输出颜色与深度到帧缓存中,所以两个纹理在每个像素上的颜色到 ...

  6. [转]解读Unity中的CG编写Shader系列3——表面剔除与剪裁模式

    在上一个例子中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上.这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁.后面 ...

  7. 解读Unity中的CG编写Shader系列3——表面剔除与剪裁模式

    在上一个样例中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上. 这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁.后 ...

  8. 解读Unity中的CG编写Shader系列六(漫反射)

    转自 http://www.itnose.net/detail/6116553.html 如果前面几个系列文章的内容过于冗长缺乏趣味着实见谅,由于时间原因前面的混合部分还没有写完,等以后再补充,现在开 ...

  9. 解读Unity中的CG编写Shader系列十 (光滑的镜面反射(冯氏着色))

    前文完成了最基本的镜面反射着色器,单平行光源下的逐顶点着色(per-vertex lighting),又称为古罗着色(Gouraud shading).这篇文章作为后续讨论更光滑的镜面反射方式,逐像素 ...

随机推荐

  1. 【300】◀▶ IDL - ENVI API

    参考:ENVI API 参考:ENVI Classic Display 序号 类名称   功能说明   语法 & 举例 01 ENVI 函数   ====<<<< De ...

  2. 将maven打包为一个jar(可以体外加入jar)

    使用 maven-compiler-plugin插件, 在maven的pom的<build></build>标签中上加入 <build> <plugins&g ...

  3. Elasticsearch-PHP 搜索操作

    搜索操作 好吧,这不叫elasticsearch的不劳而获!让我们来谈谈PHP客户端中的搜索操作. 客户端允许你通过REST API访问所有的查询和公开的参数,尽可能的遵循命名规则.让我们来看一些例子 ...

  4. axis2 webService开发指南(3)

    复杂对象类型的WebService 这次我们编写复杂点的WebService方法,返回的数据是我们定义属性带getter.setter方法JavaBean,一维数组.二维数组等 1.服务源代码 新建一 ...

  5. GL_LINES & GL_LINE_STRIP & GL_LINE_LOOP

    [GL_LINES] 独立的线段,下式中,p2与p3间没有连线. [GL_LINE_STRIP] 连续的线段,下式中, p2与p3会连成线段. [GL_LINE_LOOP] 参考:<计算机图形学 ...

  6. MyCat的安装及基本使用(MySQL分库分表)

    1.Mycat的简介 1.1 数据库集群产生的背景 如今随着互联网的发展,数据的量级也是成指数的增长,从GB到TB到PB.对数据的各种操作也是愈加的困难,传统的关系性数据库已经无法满足快速查询与插入数 ...

  7. 112. Path Sum (Tree; DFS)

    Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all ...

  8. JMeter基础使用方法

    1.修改测试计划名称 2.点击”保存“,并选择保存的路径 3.添加线程组 操作步骤:右击”测试计划“——添加——Threads——线程组 添加成功后,Test1节点下回多出一个“线程组”的节点 4.配 ...

  9. CSS 伪类和伪元素--pseudo

    总结在前: 0. 参考资料 1. 伪类和伪元素是不同的两种东西. 2. 伪类和伪元素都属于CSS选择器. 3. CSS引入伪类和伪元素是为了实现基于文档树之外的信息,i.e. 段落的第一行,的格式化. ...

  10. [Selenium With C#基础教程] Lesson-04 按钮

    作者:Surpassme 来源:http://www.jianshu.com/p/83d7416c4b7d 声明:本文为原创文章,如需转载请在文章页面明显位置给出原文链接,谢谢. Button通常有两 ...