Unity渲染和着色器笔记
重新导入后会不再显示Failedtocompile,但是人物的着色还是有问题。
变换矩阵
模型>世界
设模型的x轴y轴z轴在世界的方向是vX、vY、vX,模型的世界坐标是(xM,yM,zM)。变换矩阵是
vXx vYx vZx xM
vXy vYy vZy yM
vXz vYz vZz zM
0 0 0 1
面法向和顶点法向
面法向是垂直面的向量。顶点法向是由顶点参与的面的法向加权平均(通常是面积加权)算出的向量。模型文件里存的是顶点法向,因为面法向通过2个边叉乘就能算出。
切线空间
首先切线空间是一个顶点在它参与的一个具体三角形里的概念。就是说一个顶点参与了3个三角形,那么它有3个各自不同的切线空间。
原点在顶点,z轴是面法线,x轴是切线,y轴是副切线。切线方向是 纹理U坐标增加的方向。副切线方向是 纹理V坐标增加的方向。
对于每个三角形,可以通过顶点位置和UV坐标计算:
// 三角形的三个顶点:v0, v1, v2
// 对应的UV坐标:uv0, uv1, uv2
// 计算边向量
float3 edge1 = v1 - v0;
float3 edge2 = v2 - v0;
// 计算UV差值
float2 deltaUV1 = uv1 - uv0;
float2 deltaUV2 = uv2 - uv0;
//设:
edge1= deltaUV1.u*T+ deltaUV1.v*B;
edge2= deltaUV2.u*T+ deltaUV2.v*B;
//其中T、B是切线、副切线,三维向量
// 计算切线(T)和副切线(B)
float f = 1.0 / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
float3 tangent = f * (deltaUV2.y * edge1 - deltaUV1.y * edge2);
float3 bitangent = f * (-deltaUV2.x * edge1 + deltaUV1.x * edge2);
// 确保正交性(Gram-Schmidt正交化)
tangent = normalize(tangent - normal * dot(normal, tangent));
看不懂。我们可以想象blender里的uv展开:

这里的u和v方向,朝右朝上,把这两个方向映射回3D模型空间,就是切线、副切线。不过注意,模型三角面和3个顶点在贴图上组成的三角形不一定相似。我们可以把一个顶点拉走,让2个三角形形状明显不同:

此时我们想知道左图的u方向映射到右边空间的向量。我想到可以用v0v1、v0v2作基底表示u向量,得到基底坐标值(x,y),然后到三维空间还用(x,y)带入三维空间的v0v1、v0v2。
Phong高光和Blinn Phong高光对比
假设光和法向的夹角是a,视线和法向的夹角是b,则phong高光反射的夹角是abs(a-b),Blinn Phong高光反射的夹角是abs((a-b)/2),同样情况下Blinn Phong高光反射更亮。
纹理采样、漫反射、高光反射、环境光几个成分是相乘还是相加的问题
如果全相乘,我们知道高光反射大部分区域是黑的,乘其他成分也都是黑的,高光反射应该只对光斑附近有影响,所以+高光反射。
漫反射应该使纹理背光面变暗变淡,向光面不受影响,所以纹理*漫反射。(如果纹理+漫反射,很明显是背光面是纹理,向光面发白)
环境光呢?乘环境光,会变暗,加环境光又会加上一层白雾。所以环境光不应该在最终的公式中单独成一项,它可以给漫反射打底。或者说如果作为加法项,应该乘贴图采样,避免”白雾“效果。
最终的公式是:纹理采样*(漫反射+环境光)+高光反射。
渲染管线的流程
前向渲染
顶点坐标变换(模型空间>世界空间>相机空间>裁剪空间)得到顶点在屏幕的坐标和深度>光栅化决定每个三角形包含的像素(三次叉乘算法)>着色(根据颜色或采样贴图得到基础颜色,然后计算漫反射、高光反射、环境光决定颜色深度)>模板测试>深度测试>透明度混合>写入帧缓冲。
延迟渲染
顶点坐标变换(模型空间>世界空间>相机空间>裁剪空间)得到顶点在屏幕的坐标和深度>光栅化决定每个三角形包含的像素(三次叉乘算法)>深度测试>模板测试>写入G Buffer(像素的位置、法线)>着色>写入帧缓冲。
裁剪空间
范围(-1,-1,-1)到(1,1,1).
正交相机:原点是远裁剪面、近裁剪面中间。顶点的相机空间(x/相机半宽,y/相机半高,(2z-N-F)/(N-F))
光栅化
不偷懒的情况下,对视口里的每个三角形遍历每个像素,使用三次叉乘看符号决定像素在不在三角形里。但很明显可以取三角形3个顶点x、y坐标的最小最大值,得到包围盒,只计算包围盒里的像素。
计算颜色时颜色大于1怎么办?
根据是否启用了HDR(High Dynamic Range高动态范围),如果启用了,把颜色使用形如1-exp(-color)的公式从0-无穷大映射到0-1,没有启用则大于1的直接等于1.
.shader(ShaderLab)脚本的结构
参考文章
2024-02-01 Unity Shader 开发入门4 —— ShaderLab 语法-CSDN博客
官方文档
.shader是Unity定义的文件类型。它的基本结构为:

Shader "自定义着色器名字"
{
Properties
{
_变量名("面板显示名",类型)=默认值{选项}
...
//类型有2D:贴图、Int、Float、Range(min,max)、Color、Vector:四维向量
}
SubShader
{
Tags{"键1"="值1"...}//标签有Queue、RenderType、DisableBatching、ForceNoShadowCasting、PreviewType等
渲染状态//有Cull (Back/Front/Off)、ZWrite(On/Off)、LOD xxx、Blend等
Pass
{
Name "通道名字"//其他着色器可以通过UsePass "自定义着色器名称/通道名字"使用这个通道
Tags{"键1"="值1"...}//标签有LightMode(常用ForwardBase、ShadowCaster)
YYYPROGRAM//YYY可选CG或HLSL
YYY语言代码...
ENDYYY
}
Pass
{
YYYPROGRAM//YYY可选CG或HLSL
YYY语言代码...
ENDYYY
}
}
Fallback "备用着色器"
}
两种着色器语言对比
| CG | HLSL | |
| 全称 | C for graphics | High level shader language |
| 适配的渲染管线 | Builtin RP | URP |
其他区别:cg有fixed4数据类型,hlsl没有;
Properties
_MainTex("主贴图",2D)="white"{}
_MyFloat("浮点型参数",Float)=1
_MyRange("范围浮点型参数",Range(0,4))=1
_MyColor("颜色参数",Color)=(1,1,1,1)
_MyVec("向量",Vector)=(0,0,0,0)
_MyInt("整形参数",Int)=1
[Enum(UnityEngine.Rendering.CullMode)] _CullMode ("Cull Mode", Float) = 2
SubShader Tags
Tags{
"RenderType"="Opaque"//Transparent、TransparentCutout(树叶)、Background、Overlay
"Queue"="Geometry"//Background、Geometry、AlphaTest、Transparent、Overlay
"DisableBatching"="True"//False、LODFading
"ForceNoShadowCasting"="False"//"True"
"IgnoreProjector"="True"
}
AlphaTest和Transparent的区别:AlphaTest根据Alpha是否达到阈值,要么显示,要么不显示(用于显示形状不规则的树叶,树叶外面的区域都是低Alpha),Transparent在物体和它后面的像素之间混合。
Queue和RenderType是对应的
| Queue | RenderType |
| Geometry | Opaque |
| Transparent | Transparent |
| Background | Background |
| AlphaTest | TransparentCutout |
| Overlay | Overlay |
渲染状态
Cull Back//Off、Front、[_CullMode]材质面板控制
ZWrite On//Off
ZTest Less//Greater、LEqual、GEqual、Equal、NotEqual、Always
Blend SrcAlpha OneMinusSrcAlpha//One One、OneMinusDstColor One、DstColor Zero
//DstColor SrcColor、One OneMinusSrcAlpha
LOD 100
不同Blend对比
SrcAlpha OneMinusSrcAlpha

One One:颜色相加

OneMinusDstColor One

DstColor Zero:颜色相乘

DstColor SrcColor:二倍颜色相乘

Pass Tags
Tags{"LightMode"="ForwardBase"
//Always、ForwardAdd、Deferred、ShadowCaster、……
"RequireOptions"="SoftVegetation"
"PassFlags"="OnlyDirectional"
}
GrabPass
GrabPass
{
"_BackgroundTexture"
}
//把当前屏幕输出到_BackgroundTexture
Properties和Pass变量对应
| Color,Vector | float4,half4,fixed4 |
| Range,Float,Int | float, half, fixed |
| 2D | sampler2D |
顶点着色器和片元着色器
着色器分为两个阶段:顶点着色器Vertex shader和片元着色器Fragment shader。通过
#pragma vertex xxx
#pragma fragment yyy
指定顶点着色器函数为xxx,片元着色器函数为yyy。着色器脚本里还会定义两个结构体:顶点着色器的输入结构体(一般叫appdata)、顶点着色器传递到片元着色器的结构体(一般叫v2f)。顶点着色器的输出类型和片元着色器的输入类型虽然一样,但中间发生了从逐顶点到逐像素的插值,frag的输入结构体数量等于像素数量,远大于vert的输出结构体数量(等于顶点数量)。
语义
就是着色器脚本里全大写的量:POSITION, TEXCOORD0等,对应cpu或gpu里的寄存器。在顶点着色器的输入结构体的字段后面加:POSITION意为从cpu的这个寄存器取数据,取出的是顶点在模型本地的坐标。其他语义类似。
在顶点到片元结构体的字段后面加:XXX意为写入到gpu的这个寄存器。
对TEXCOORD0的理解
TEXCOORD里面存的是和模型顶点数量相同的float2。appdata里的:TEXCOORD0是从模型顶点数据取数据。而在v2f里的:TEXCOORD0的意思是:
在 v2f 结构体(vertex-to-fragment)中,TEXCOORD0 的含义与 appdata 中不同,它不是模型数据,而是插值寄存器,用于在顶点着色器和片元着色器之间传递数据。
struct v2f
{
float2 uv : TEXCOORD0; // ← 插值寄存器(用于传递数据)
};
TEXCOORD0 在这里是一个语义标签,告诉GPU:"请把这段数据在光栅化时进行插值,然后传递给片元着色器"。
在 v2f 结构中:
-
TEXCOORD0 是插值寄存器语义
-
用于顶点→片元的数据传递
-
GPU会在光栅化时自动插值
-
可以传递任意数据(不限于UV坐标)
-
数字编号只是标识符,没有特殊含义
系统值System value
命名为SV_XXX的语义叫系统值,是gpu拿去渲染用的数据。常见的有SV_POSITION是顶点在裁剪空间的坐标,SV_TARGET是顶点的颜色值。
顶点位置空间转换和向量(比如法线)空间转换不能用一个转换矩阵吗?
我们这样想,模型空间比世界空间平移了(3,0,0),旋转、缩放都不变化,那么一个向量,它在这两个坐标系的方向是一样的,但是一个点在两个坐标系的位置不一样。
所以顶点位置空间转换矩阵是4x4,向量空间转换矩阵是3x3。
向量的空间转换相当于两个顶点空间转换后相减。
编译预处理指令#pragma
#pragma的作用很多,无法用一句话概括。它的具体作用取决于后面的第一个参数。如上面指定顶点和片元着色器。
对#pragma multi_compile的理解
参考文章
【unity shader变体之#pragma multi_compile 和 #pragma shader_feature - CSDN App】https://blog.csdn.net/qq_17347313/article/details/106872268?sharetype=blogdetail&shareId=106872268&sharerefer=APP&sharesource=Jesui&sharefrom=link
和#define类似,都是条件编译,而#pragma multi_compile只是声明一组“候选”宏,后面的宏也不一定启用,需要shader.EnableKeyWord()或material.EnabledKeyWord()才相当于#define了这个宏。条件编译时也是用#if defined()。相当于用非编译预处理指令定义宏。
LOD
官方介绍
ShaderLab:为子着色器指定 LOD 值 - Unity 手册
首先着色器LOD和模型LOD是两个概念。通过设置shader.maximumLOD,在一列subshader里使用第一个遇到的不大于maximumLOD的subshader(如果想使用尽量高品质的着色器就把LOD降序排列)。它就是一个着色器选择器。
HLSL的语法和API
CBUFFER_START()和CBUFFER_END
这是hlsl语言用的,用来把Properties里不是贴图(对所有顶点一样,也就是uniform)的参数搬运过来,在程序里使用。
Shader "自定义着色器"
{
Properties
{
_AAA("哈哈哈",Float)=0
}
SubShader
{
Pass
{
Name "通道1"
HLSLPROGRAM
CBUFFER_START(UnityPerMaterial)
float _AAA;
ENDHLSL
}
}
Fallback "备用着色器"
}
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
VertexPositionInputs是一个结构体,里面包含顶点在各个空间下的坐标。GetVertexPositionInputs(float3 positionOS)用于从模型空间得到各个空间的坐标。
阴影
产生阴影的原理是光源每帧根据相机的位置、方向,确定一个“阴影相机”的覆盖范围,记录“阴影相机”画面各位置的深度,生成一个阴影纹理。在v2f中使用SHADOW_COORDS(idx)声明阴影坐标变量。在顶点着色器,TRANSFER_SHADOW(o)计算此顶点在阴影纹理的坐标,存在_ShadowCoord。片元着色器里SHADOW_ATTENUATION(i)根据i._ShadowCoord和阴影纹理存的深度比较,大于存的深度就是阴影。
阴影Pass,阴影投射者和接收者都需要应用这种材质。
Pass
{
Tags{"LightMode" = "ShadowCaster"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd_fullshadows // 支持多重编译,包含完整阴影
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
// 定义顶点到片段的数据结构,与基础Pass一致
struct v2f
{
float4 pos : SV_POSITION;
float4 vertex : TEXCOORD1;
SHADOW_COORDS(2)
};
fixed4 _MainColor;
// 顶点着色器
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.vertex = v.vertex;
TRANSFER_SHADOW(o)
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 转换顶点到世界空间
float4 worldPos = mul(unity_ObjectToWorld, i.vertex);
// 使用阴影宏计算阴影系数
UNITY_LIGHT_ATTENUATION(shadowmask, i, worldPos.rgb)
fixed4 color = _LightColor0 * _MainColor;
// 应用阴影到颜色
color.rgb *= shadowmask;
return color; // 返回最终颜色
}
ENDCG
}
内置代码
按常用程度。变量类宏定义一般以_开头,常量类宏定义全大写。
UnityObjectToClipPos(float4 v)
tex2D(贴图,uv)
uv范围是0-1,对应贴图从左到右,从下到上。当uv超过(0,1)时根据贴图的Wrap Mode决定是就采样0、1还是循环采样。
那么下面这一段:
fixed4 col=tex2D(_MainTex,i.vertex.xy/100);
就把此材质的物体变成了一个“窗口”,贴图以一定缩放平铺在屏幕里,在模型显示的区域能看见贴图。
float3 UnityObjectToWorldNormal(float3 norm)
_WorldSpaceLightPos0
从物体表面指向光源的方向。类型为float4,分量w为0表示平行光,1表示点光源。
_LightColor0
主光源颜色。需要包含"Lighting.cginc"
_WorldSpaceCameraPos
相机世界位置。
UNITY_LIGHTMODEL_AMBIENT
类型float4,表示环境光的颜色。
unity_ObjectToWorld
模型转世界空间的矩阵。
reflect(i,n)
计算反射方向。
float3 WorldSpaceViewDir(float4 v)
float3 ObjSpaceViewDir(float4 v)
float3 WorldSpaceLightDir(float4 v)
flaot3 ObjSpaceLightDir(float4 v)
模型空间,顶点指向光源的方向。对平行光,所有顶点相同。
float3 UnityObjectToWorldDir(in float3 dir)
float3 UnityWorldToObjectDir(float3 dir)
UnityObjectToWorldNormal和UnityObjectToWorldDir的内部有什么区别?
主要在于如何处理非均匀缩放。
| 特性 | UnityObjectToWorldNormal | UnityObjectToWorldDir |
|---|---|---|
| 用途 | 专门用于法线向量 | 用于一般方向向量(切线、视线、光源方向等) |
| 缩放处理 | 使用逆转置矩阵处理非均匀缩放 | 使用普通旋转矩阵,忽略缩放或假设均匀缩放 |
| 数学原理 | 乘以模型矩阵的逆转置 | 乘以模型矩阵的旋转部分 |
| 归一化 | 自动重新归一化 | 通常不重新归一化(除非输入已归一化) |
| 典型用途 | 法线贴图、光照计算 | 切线方向、向量运算 |
_MainTex_TexelSize
贴图像素尺寸。
具体来说,这个变量是一个四元数(Vector4),包含以下四个分量值:
- x:1 / 纹理宽度(每个像素在UV空间的宽度)
- y:1 / 纹理高度(每个像素在UV空间的高度)
- z:纹理宽度(像素数量)
- w:纹理高度(像素数量)
假设你的纹理分辨率是512×512像素,那么_MainTex_TexelSize的值就是:
Vector4(1/512, 1/512, 512, 512)
采样一个像素的上下左右像素:
half2 destUv = half2(_MainTex_TexelSize.x, _MainTex_TexelSize.y);
half spriteLeft = tex2D(_MainTex, i.uv + half2(destUv.x, 0)).a;
half spriteRight = tex2D(_MainTex, i.uv - half2(destUv.x, 0)).a;
half spriteBottom = tex2D(_MainTex, i.uv + half2(0, destUv.y)).a;
half spriteTop = tex2D(_MainTex, i.uv - half2(0, destUv.y)).a;
TRANSFORM_TEX(uv,tex)
TRANSFORM_TEX(v.uv,_MainTex)应用贴图的拉伸、偏移。相当于
v.uv.xy*_MainTex##_ST.xy+_MainTex##_ST.zw
_Time
_Time 是一个float4类型的变量,包含四个分量:
- x分量:t/20(时间除以20)
- y分量:t(原始时间)
- z分量:t×2(时间乘以2)
- w分量:t×3(时间乘以3)
其中t是从当前场景加载开始所经过的时间(以秒为单位)
着色器应该怎么学
写着色器有几个特点
- 没有打印功能,难调试;
- 没有代码提示;
- 大量用到UnityCG.cginc、AutoLight.cginc的宏定义;
着色器代码调试
直接输出查看:
return fixed4(result,result,result,result);
实战
逐顶点Phong
这里没有加单独的漫反射颜色,不想让参数太多。
Shader "逐顶点Phong"
{
Properties
{
_Color ("Color", Color) =(1,1,1,1)
_SpeColor("高光颜色",Color)=(1,1,1,1)
_Gloss("高光范围",Range(0.1,10))=5
_MainTex("MainTex",2D)="white"{}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv:TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 spec:TEXCOORD0;
fixed3 albedo:TEXCOORD2;
float2 uv:TEXCOORD1;
};
fixed4 _Color;
fixed4 _SpeColor;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
float3 worldNormal=normalize(UnityObjectToWorldNormal(v.normal));
float3 lightPos=normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse=_LightColor0.rgb*_Color*max(0,dot(worldNormal,lightPos));
float3 lightDir=-normalize(_WorldSpaceLightPos0.xyz);
float3 reflDir=reflect(lightDir,worldNormal);
float3 worldVer=mul(unity_ObjectToWorld,v.vertex);
float3 viewDir=normalize(_WorldSpaceCameraPos-worldVer);
fixed4 spec=_LightColor0*_SpeColor*pow(max(0,dot(reflDir,viewDir)),_Gloss);
o.albedo=diffuse+UNITY_LIGHTMODEL_AMBIENT;
o.spec=spec;
o.uv=v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 color=tex2D(_MainTex,i.uv)* i.albedo+ i.spec;
return fixed4(color,1);
}
ENDCG
}
}
}
透明度随时间变化
注意要支持透明必须写
Tags{
"Queue"="Transparent"
}
Blend SrcAlpha OneMinusSrcAlpha
Shader "透明度随时间变换"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_RandomSeed("_MaxYUV", Range(0, 10000)) = 0.0
}
SubShader
{
Tags{
"Queue"="Transparent"
}
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
UNITY_DEFINE_INSTANCED_PROP(float, _RandomSeed)
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half randomSeed = UNITY_ACCESS_INSTANCED_PROP(Props, _RandomSeed); // 获取随机种子值
fixed4 col = tex2D(_MainTex, i.uv);
col.w=(_Time.y + randomSeed) % 1;
return col;
}
ENDCG
}
}
}
裁剪空间坐标z的含义
用这个代码测试:
fixed4 frag (v2f i) : SV_Target
{
fixed col=i.vertex.z;
return col;
}


看起来相机处是1,远离相机快速变成0.
描边
参考:
Quick Outline | Particles/Effects | Unity Asset Store
它是由2个材质Mask和Fill组成,Mask的队列值小于Fill,比Fill先渲染。Fill负责把物体渲染成纯色且比真实形状大一些,通过
output.position = UnityViewToClipPos(viewPosition + viewNormal * -viewPosition.z * _OutlineWidth / 1000.0);
把顶点向外移一些。而Mask先通过模板测试,使用
Stencil {
Ref 1
Pass Replace
}
把模板缓冲区写成1,然后Fill的模板设置
Stencil {
Ref 1
Comp NotEqual
}
凡是Mask渲染过的像素,模板缓冲区是1,Fill通不过测试,不渲染,显示之前的像素。这需要Mask在Fill之前渲染,如果把Mask的队列改成大于3110,它就在Fill后面渲染,就会失效,物体变成纯色。
我没有搞懂的是Mask里没有顶点着色器,没有输出到SV_Position,怎么知道Mask覆盖的区域的?
漫反射逐顶点和逐像素的对比
逐顶点:v2f里记录颜色;vert里计算漫反射颜色;frag里乘颜色;
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
fixed3 color:COLOR;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
fixed3 worldNormal=normalize(UnityObjectToWorldNormal(v.normal));
fixed3 worldLight=normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse=_LightColor0.rgb*saturate(dot(worldNormal,worldLight));
o.color=diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb=col.rgb*i.color;
return col;
}
逐像素:v2f里记录法线;vert里计算法线;frag里计算漫反射颜色,应用;
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldNormal:TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 worldLight=normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse=_LightColor0.rgb*saturate(dot(worldNormal,worldLight));
col.rgb*=diffuse;
return col;
}
UnityURPToonLitShaderExample着色器有时候会显示在Failed to compile里:


重新导入后会不再显示Failed to compile
UnityURPToonLitShaderExample着色器应用后,关闭项目再打开,妮露和甘雨的着色出现黑色:

把人物删掉,重新导入,应用Shader,人物显示正常:

关闭项目重新打开后又出问题。

妮露的头发的渲染由两个材料完成,“前蝴蝶结”渲染大部分颜色:

“后发”渲染头发上的光泽,大部分是黑的:(可以看到出问题的时候头发光泽还在)

推测原因是把“后发”这个材料覆盖在了“前蝴蝶结”上。
解决方法:后发材料勾选Alpha Clipping。这个选项的意思是把贴图里不透明度alpha值不大于Cutoff的部分设置为透明,让其他材料渲染。

关于下面的Cutoff:为0时预览材质球跟之前一样,大部分是黑的;稍微调大一点黑色消失,只剩下光泽;再调大一点光泽也消失。但是调这个对场景里的效果没影响。
但是甘雨的后背材料“肌”勾选Alpha Clipping没用。给衣服的材料“裙摆”勾选Alpha Clipping有用:

同时发现“裙摆”预览材质球的一些部分由黑色变透明,所以是“裙摆”该设置透明的部分没有透明,渲染成黑色,盖在了后背的材料“肌”上。
总之,所有预览材质球里有这种黑色部分的材料都应该勾选Alpha Clipping。如果没有,勾不勾选就没有区别。

从顶点着色器到片元着色器的结构体里的一个变量没有写语义

系统会自动加上这一段:

给着色器加阴影效果


解决方法:在Shader最后加Fallback "Diffuse",GPU用自定义的着色器渲染完会调用Diffuse着色器,Diffuse着色器里面有LightMode=ShadowCaster的Pass,用于投射阴影。

投射阴影需要在shader脚本里定义LightMode=ShadowCaster的pass并在里面写相应代码。使用了Fallback Diffuse后如果自己写的脚本没有LightMode=ShadowCaster的pass就会使用Diffuse的。
背面剔除
背面剔除后从模型内部看不到模型。
URP内置的Lit和Unlit着色器的背面剔除叫Render Face。


自己写代码是声明一个[Enum(UnityEngine.Rendering.CullMode)]类型的变量,再在Pass开头写Cull[变量名]

某些人的衣服颜色不对
着色器是SimpleURPToonLitExample(With Outline)和URP/Lit会有此问题。URP/Unlit没问题。

更奇怪的是改变BlendShapes衣服会变色:

改成别的着色器如URP/Unlit,好了。所以认为是着色器不完善,细节无法深究。

Instancing: Property 'unity_RenderingLayer' shares the same constant buffer offset with 'unity_LODFade'. Ignoring.
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

移动相机时场景里的树明暗突变,可能是这个报错的结果。

后来我想起我导入了URP的例子,删除例子,场景里的东西变成淡蓝色,重新渲染了。然后问题消失了。不过具体机制还是不懂。

场景里没有阴影时可以检查哪些地方
Project settings-Graphics-Scriptable render pipeline settings点进URP Asset的检查器,在Lighting部分检查Cast Shadows

在场景里的光源检查器的Shadows部分检查Shadow Type。

物体的Mesh renderer或Skinned mesh renderer的Lighting部分的Cast shadows

总之,检查Asset和Component的设置。
试图写接收阴影的着色器,失败
按照
Unity中Shader阴影的接收_shader 阴影-CSDN博客
写的,没有看到其他物体的阴影在人物上造成明暗差异的效果。v2f中定义了SHADOW_COORDS()而非UNITY_SHADOW_COORDS(),因为查阅AutoLight.cginc已经改成这个名字了。
Shader "学习/贴图加漫反射"//名称
{
Properties
{
_MainTex ("贴图", 2D) = "white" {}
[Enum(UnityEngine.Rendering.CullMode)]_CullMode("剔除模式",float)=2
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass//一次渲染过程
{
Cull [_CullMode]//关闭背面剔除Cull Back背面剔除
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert//顶点着色器声明,类型 名字
#pragma fragment frag//片元着色器声明,类型 名字
#pragma multi_compile DIRECTIONAL POINT SHADOWS_SCREEN
#include "Lighting.cginc"
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata//从CPU拿的数据结构,名字可自定义
{
float4 vertex : POSITION;//顶点在模型空间下的坐标,vertex是变量名,可自定义;POSITION特定语义词
float2 uv : TEXCOORD0;//第一套uv
float3 normal : NORMAL;
};
struct v2f//从顶点着色器传递到片元着色器的数据结构,名字可自定义
{
float2 uv : TEXCOORD0;//储存器
float4 vertex : SV_POSITION;
float3 normalInWorld:NORMAL;
float4 worldPos :TEXCOORD2;
SHADOW_COORDS(3)
};
sampler2D _MainTex;//把Properties里的同名变量传进SubShader
float4 _MainTex_ST;//贴图拉伸和偏移
v2f vert (appdata v)//顶点着色器
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);//应用贴图拉伸和偏移
o.normalInWorld=mul((float3x3)unity_ObjectToWorld,v.normal);
TRANSFER_SHADOW(o)
o.worldPos = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target//片元着色器。冒号后面是渲染目标
{
//贴图采样
fixed4 col = tex2D(_MainTex, i.uv);//fixed精度8位,half精度16位
//下面是计算漫反射
fixed3 worldNormal=normalize(i.normalInWorld);
//获得直射光的方向
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
//漫反射
fixed3 diffuseColor=_LightColor0.rgb*col.rgb*(dot(worldNormal,worldLightDir)*0.5+0.5);
//增加环境光颜色
fixed3 color=col.rgb;
color*=UNITY_LIGHTMODEL_AMBIENT.xyz;
color+=diffuseColor;
//接收阴影
// float attenuation=1;
// attenuation= SHADOW_ATTENUATION(i);
UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
color*=attenuation;
return fixed4(color,1);
}
ENDCG
}

又尝试把UNITY_LIGHT_ATTENUATION输出的值返回:
UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
color*=attenuation;
return fixed4(attenuation,attenuation,attenuation,1);
人物显示为全白色,也就是全1:

开头加入
#pragma multi_compile_fwdbase
则TRANSFER_SHADOW(o)的位置报错

让SimpleURPToonLitOutlineExample着色器接收阴影
接收阴影的原理:
- 使用一个世界坐标转阴影贴图坐标的函数(HLSL里是TransformWorldToShadowCoord())得到模型顶点的阴影坐标;
- 把阴影坐标输入一个计算阴影衰减的函数(HLSL里是MainLightRealtimeShadow(float4 shadowCoord))得到衰减值,就是一个浮点数;
- 应用阴影衰减,通常是把输出的颜色乘上衰减。
SimpleURPToonLitOutlineExample里的修改方法:
1.开启关键字法
1.选中要接收阴影的材质,在右上角三个点开启Debug;

2.在Valid Keywords下加一个_MAIN_LIGHT_SHADOWS;

注意加关键字的时候可能在下面的Invalid Keywords加了一个元素,不用管,把关键字粘贴进去,如果正确就会进入Valid Keywords里面。
效果:

2.修改代码法
1.在SimpleURPToonLitOutlineExample_Shared.hlsl的InitializeLightingData()函数里加上lightingData.shadowCoord=TransformWorldToShadowCoord(input.positionWSAndFogFactor.xyz);加上之后这个函数变成:
ToonLightingData InitializeLightingData(Varyings input)
{
ToonLightingData lightingData;
lightingData.positionWS = input.positionWSAndFogFactor.xyz;
lightingData.viewDirectionWS = SafeNormalize(GetCameraPositionWS() - lightingData.positionWS);
lightingData.normalWS = normalize(input.normalWS); //interpolated normal is NOT unit vector, we need to normalize it
lightingData.shadowCoord=TransformWorldToShadowCoord(input.positionWSAndFogFactor.xyz);
return lightingData;
}
2.在ShadeAllLights()函数里的GetMainLight输入lightingData.shadowCoord:
Light mainLight = GetMainLight(lightingData.shadowCoord);
相当于把#ifdef _MAIN_LIGHT_SHADOWS #endif里的代码拿出来了。
关键函数:
InitializeLightingData();
ShadeAllLights();
ShadeSingleLight();
Develop build里树的近距离模型看不见
解决方法:在场景里放一个树的预制体,不要只把树用于地形工具绘制树。😓
更多推荐


所有评论(0)