8. Advanced effects
42. Lighting in a vertex, fragment shader
42.1 Lambert Lighting
- Convert the modelled normal to world space
- Get the dot product of the world normal and the light direction
- CG function max(a,b) returns the larger of the two numbers, a and b.
- Use nl to adjust the diffuse color 通过nl去控制光照漫反射强度,
_LightColor0.rgb
是本来应该的漫反射颜色,nl调控。
42.2 添加环境光
为什么地面现在如此黑暗呢?是因为我们没有接收环境光。
什么是环境光呢?我们用环境光来模拟间接光照。
什么是间接光照呢?也就是当光线在多个物体间反射然后进入摄像机。比如红地毯上放一个浅灰色沙发,那么沙发底部就会有红色。
- 在
v2f
中添加fixed3 ambient : COLOR1
- 在vert中赋值
o.ambient = ShadeSH9(half4(worldNormal,1));
- 在frag中添加
fixed3 lighting = i.diff*shadow + i.ambient
42.2.1 ShadeSH9是什么?
球谐函数,在BRDF中用来计算间接光的漫反射。
什么是球谐?球谐函数就是根据环境光漫反射生成的环境光贴图。
Unity计算的方式ShadeSH9(float4(i.normal,1))
.
42.3 添加Shadow
program multi_Compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
将shader编译成多个带或者不带阴影的变体,由于现在还不关心这些lightmap变体,所以跳过.#include "AutoLight.cginc"
shadow 宏命令,所有的shadow相关的命令都在这个里面SHADOW_COORDS(1)
v2f结构体,把阴影放到texcoord1- vert中
TRANSFER_SHADOW(o)
在顶点着色器中计算上一步中声明的阴影纹理坐标 fixed shadow = SHADOW_ATTENUATION(i);
在片元着色器中计算阴影值。
Shader "NiksShaders/Shader60Lit"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//将shader编译成多个带或者不带阴影的变体
//由于现在还不关心这些lightmap变体,所以跳过
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
#include "UnityCG.cginc"
#include "Lighting.cginc"
//shadow 宏命令,所有的shadow相关的命令都在这个里面
#include "AutoLight.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
SHADOW_COORDS(1) //把阴影放到texcoord1
float4 pos : SV_POSITION;
fixed3 diff : COLOR0;
fixed3 ambient : COLOR1;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
half3 worldNormal = UnityObjectToWorldNormal(v.normal);
half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
o.diff = nl * _LightColor0.rgb;//控制漫反射颜色
o.ambient = ShadeSH9(half4(worldNormal,1));
// compute shadows data
TRANSFER_SHADOW(o)
return o;
}
sampler2D _MainTex;
fixed4 frag (v2f i) : SV_Target
{
fixed3 col = tex2D(_MainTex, i.uv).rgb;
// compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
fixed shadow = SHADOW_ATTENUATION(i);
// darken light's illumination with shadow, keep ambient intact
fixed3 lighting = i.diff * shadow + i.ambient;
col *= lighting;
return fixed4(col, 1);
}
ENDCG
}
// shadow casting support
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
}
}
43. A position location shader
思路就是通过脚本设置_Position,然后在Position的地方沿着xz方向画一个图形。
加入可控制的属性,最关键的属性_Position
,在v2f中声明worldPos : TEXCOORD2
存起来,在顶点着色器中利用mul将顶点变到世界空间o.worldPos = mul(unity_ObjectToWorld,v.vertex);
,然后col+=_CircleColor* circle(i.worldPos.xz,_Position.xz,_Radius,0.1,0.001);
画圆,lerp也可以。
Shader "NiksShaders/Shader61Lit"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Position("Position",Vector) = (0,0,0,0)
_Radius("Radius",Float) = 1
_CircleColor("Circle Color",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
// compile shader into multiple variants, with and without shadows
// (we don't care about any lightmaps yet, so skip these variants)
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
// shadow helper functions and macros
#include "AutoLight.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
SHADOW_COORDS(1) // put shadows data into TEXCOORD1
fixed3 diff : COLOR0;
fixed3 ambient : COLOR1;
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD2;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
half3 worldNormal = UnityObjectToWorldNormal(v.normal);
half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
o.diff = nl * _LightColor0.rgb;//控制漫反射颜色
o.ambient = ShadeSH9(half4(worldNormal,1));
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
// compute shadows data
TRANSFER_SHADOW(o)
return o;
}
sampler2D _MainTex;
float4 _Position;
float _Radius;
fixed4 _CircleColor;
//画圆函数,pt是测试点
float circle(float2 pt, float2 center, float radius, float line_width, float edge_thickness){
float2 p = pt - center;
float len = length(p);
float half_line_width = line_width/2.0;
float result = smoothstep(radius-half_line_width-edge_thickness, radius-half_line_width, len) - smoothstep(radius + half_line_width, radius + half_line_width + edge_thickness, len);
return result;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 col = tex2D(_MainTex, i.uv).rgb;
// compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
fixed shadow = SHADOW_ATTENUATION(i);
// darken light's illumination with shadow, keep ambient intact
fixed3 lighting = i.diff * shadow + i.ambient;
col *= lighting;
//col+=_CircleColor* circle(i.worldPos.xz,_Position.xz,_Radius,0.1,0.001);
fixed3 incircle = circle(i.worldPos.xz,_Position.xz,_Radius,0.1,0.001);//添加的
fixed3 finalCol = lerp(col,_CircleColor,incircle);//添加的
return fixed4(finalCol, 1);
}
ENDCG
}
// shadow casting support
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
}
}
44. Drawing a cross on the floor
自己的感觉还不错。
float cross(float2 pt,float2 center,float size,float line_width, float edge_thickness,float theta)
{
float c = cos(theta);
float s = sin(theta);
pt = mul(float2x2(c,s,-s,c),pt - center) + center;//将测试点逆时针旋转
float2 p = pt - center;
float half_line_width = line_width/2.0;
float result = smoothstep(-half_line_width - edge_thickness,-half_line_width,p.x) - smoothstep(half_line_width - edge_thickness,half_line_width,p.x);
result += smoothstep(-half_line_width - edge_thickness,-half_line_width,p.y) - smoothstep(half_line_width - edge_thickness,half_line_width,p.y);
float finalResult =(1 - step(size * 0.5,length(p))) * result;
return finalResult;
}
老师的
float cross(float2 pt, float2 center, float radius, float line_width, float edge_thickness){
float2 p = pt - center;
float len = length(p);
float half_line_width = line_width/2.0;
float result = 0;
if (len<radius){
float horz = smoothstep(-half_line_width-edge_thickness, -half_line_width, p.y) - smoothstep( half_line_width, half_line_width + edge_thickness, p.y);
float vert = smoothstep(-half_line_width-edge_thickness, -half_line_width, p.x) - smoothstep( half_line_width, half_line_width + edge_thickness, p.x);
result = saturate(horz + vert);
}
return result;
}c
45. Tessellation 曲面细分
也不知道有什么用,先不写了~~
46. Using the stencil buffer
46.1 Redering a scene - frame buffer(帧缓冲)
帧缓存器,在CG/HLSL中,帧缓存器是用来当做颜色缓冲区,作为存储一个像素。流程是模版测试—深度测试—帧缓冲。一帧画面上的某一个像素基本上只有一个模版缓冲区,一个深度缓冲区,一个帧缓冲区。
利用32bit来存储一个RGBA,每个通道8bit。也就是利用frame buffer来存放像素绘制结果。
46.2 Rendering a scene - z buffer(深度缓冲)
z buffer用来记录frame buffer上,相机到渲染像素之间的距离,也就是z值。
if current fragment distance < current z buffer
then overrite frame buffer and update z buffer
如果目前的片元z值 < z buffer 中的z值,就更新zbuffer,然后覆盖帧缓冲中的像素。
(Unity基于左手系,摄像机z向正方向,z小代表距离近。OpenGL基于右手系,面向负轴)
46.3 Rendering a scene - stencil buffer
the same size as frame buffer。也就是说,模版测试和深度测试可以将多个物体渲染关联起来,模版测试可以通过模版来规定一个界限,通过就执行一个操作和不通过直接抛弃片元。
46.3.1 Unity提前定义的5个渲染队列 ——Queue
索引号小的先渲染,大的后渲染。
若果发现Unity下你的模板测试效果不对,但模板里Ref这些都没问题的话,那估计是RenderQueue设置的不合适。一般Mask的RenderQueue低于被Mask的对象。
Queue参数
名称 | 队列索引号 | 描述 |
---|---|---|
Background | 1000 | 最先渲染,背景物体 |
Geometry | 2000 | 默认渲染,大多数物体,不透明物体 |
AlphaTest | 2450 | 要进行透明度测试的物体。 |
Transparent | 3000 | 这里后往前渲染,使用了透明度混合(例如关闭深度写入的shader)使用。 |
Overlay | 4000 | 叠加效果,最后渲染的物体。 |
总的来说就是先渲染不透明的,再渲染透明的。
46.3.2 想让Quad不渲染自己的 ColorMask方法
ColorMask 0
——写在CG前,通知unity不要渲染这个物体frame buffer中的像素,
ZWrite Off
但是这时还不会影响scene的frame buffer,关闭深度写入是防止透明物体后面更远的物体被剔除。因为深度写入就是用来通过深度决定渲染哪个的。
Stencil{
Ref 1 //参考值,如果写入模版缓冲区,写入值就是1
Comp always//将参考值和当前缓冲区内容比较
Pass replace//通过测试怎么做,这里就是替换模版缓冲区的值为1
}
Stencil 常用参数
参数 | 说明 |
---|---|
Ref | 参考值,参数允许赋值时候会赋值给当前像素,范围0~255 |
Comp | 比较方法,将当前的render pixel定义参考值和模版缓冲区的参考值进行比较,如果XX |
Pass | 模版测试和深度测试都通过,处理 |
Fail | 都失败处理 |
ZFail | 模版通过,深度测试失败处理 |
Comp常用参数
Comp | means |
---|---|
Greater | Render pixels with Ref greater than buffer value.大于 |
GEqual | Render pixels with Ref greater or equal to buffer value.大于等于 |
Less | Render pixels with Ref less than buffer value.小于 |
LEqual | Render pixels with Ref less than or equal to buffer value小于等于 |
Equal | Render pixels with Ref equals to buffer value. |
NotEqual | Render pixels where Ref differs from buffer value. |
Always | Make the stencil test always pass.让模版测试通过 |
Never | Make the stencil test always fail.让模版测试失败 |
Pass常用参数
Pass | Means |
---|---|
Keep | Keep buffer contents 默认是0,每帧刷新 |
Zero | Write 0 to buffer |
Replace | Write Ref value to buffer |
IncrSat | Increment buffer value. Capped at 255. |
DecrSat | Decrement buffer value. Capped at 0. |
Invert | Negate the bits |
IncrWrap | Increment buffer. 255 becomes 0. |
DecrWrap | Decrement buffer. 0 becomes 255. |
两个shader如下:
不透明的物体:
Shader "NiksShaders/Shader63aLit"
{
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Tags { "Queue" = "Geometry" }
stencil
{
Ref 1
Comp NotEqual//不等于1的保持,等于1的舍弃
Pass Keep
}
Cull Back//需要被穿透的前面通道
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
Tags { "Queue" = "Geometry" }
stencil
{
Ref 1
Comp always//通过测试,不会被舍弃
Pass Keep
}
Cull Front//后面通道
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
遮罩物体:
Shader "NiksShaders/Shader63bLit"
{
Properties {
}
SubShader {
Tags { "Queue" = "Geometry-1" }
ColorMask 0
ZWrite Off
stencil
{
Ref 1
Comp always
Pass replace
}
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float3 worldPos;//Because empty Input causes an error
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = fixed4(1,1,1,1);
}
ENDCG
}
Fallback "Diffuse"
}
43.3.3 Tips:遮罩要写入模版缓冲
我的理解:模版测试和深度测试通过复杂判断(通过测试可能改变模版缓冲区和深度缓冲区的值),来决定该片元最终是否被丢弃,不改变其他任何信息。
不通过直接丢弃,通过可以选择性改变缓冲区值,区别是深度测试关闭也可以选择写入。
When writing to the stencil buffer the shader must be earilier in the Queue then shaders reading from the buffer. 写入模版缓冲的shader必须队列比读取模版缓冲区的shader早渲染(Queue小)——这里具体体现在遮罩要先渲染,因为遮罩要写入模版缓冲
The Comp operation decides which pixels are able to write to the frame buffer
Comp决定哪些像素能够写到帧缓冲,也就是会被渲染
The Pass operation decides which pixels write to the stencil buffer.
Pass决定哪些像素写到模版缓冲区
ColorMask 0
47.Clipping the output
47.1 CG function clip
- If the parameter passed to clip is less than 0
- Output to the frame, z and stencil buffers will be ignored.
如果clip(a),a < 0
那么该像素就会被裁剪掉。
传统瓦片化的方式,利用frac函数,将位置放大倍数,取小数,这样就会导致每个位置都是(0,1):clip(frac(IN.worldPos.y*10) - (_SinTime.w + 1)/2);
定义出现时间:clip(frac(IN.worldPos.y * 10)-(1.0 - RevealTime));
当RavelTime是1的时候就会显示。