flat surface shaderr 怎么加像素

unity3d里的surface shader中的光照函数是基于定点还是像素的? - 知乎8被浏览336分享邀请回答21 条评论分享收藏感谢收起写回答Shader编程学习笔记(八)—— Surface Shader 2
时间: 12:57:50
&&&& 阅读:760
&&&& 评论:
&&&& 收藏:0
标签:Surface Shader
  上一小结主要了解了Surface Shader使用了&#pragma surface surf Standard fullforwardshadows&指令的意义,这一小节主要了解&surf&surface函数。
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _C
o.Albedo = c.
// Metallic and smoothness come from slider variables
o.Metallic = _M
o.Smoothness = _G
o.Alpha = c.a;
  surface函数总是无返回的,但在Unity当中要渲染物体,一个函数总是要输出最后的结果,这个"surf"函数没有返回结果,实际上它并没有以返回值的形式进行输出,它有两个参数:&Input IN&和&inout SurfaceOutputStandard o&。
  第一个参数&Input IN&就是前面定义的&Input&结构体,在这个结构体中当前只有主纹理的uv坐标。关于这个纹理坐标我们可以查看一下Unity手册中的,在&Surface Shader input structure&中描述了除了使用&uv&作为开头的成员外,还有其他的成员,这些主要用于光照的计算,关于这些内容的使用,只有在系统地学习Cg语法、Vertex & Fragment Shader程序设计和计算机图形学的一些经典光照算法后,才能更灵活地使用这些功能,目前我们只要了解在&Input&结构体当中的纹理uv坐标值的功能。
  第二个参数使用一个特性"inout",这个特性是Cg语言中比较重要的内容。如果在参数前没有任何修饰,那么默认是指输入的;用&out&修饰的,它的值在最后当做输出,并且在外部可以被直接使用。如果用&inout&,它描述的是这个参数既是输入的也是输出的。这个"surf"函数虽然没有返回值,但是它的第二个参数是有输出功能的。
  第二个参数的类型是&SurfaceOutputStandard&,关于这个类型可以查看一下官方手册,有一个结构体为&SurfaceOutput&,是Unity5.0之前使用的,而在Unity5.0当中,&SurfaceOutput&具有不同的形态,它演变成两种结构体,分别是&SurfaceOutputStandard&和&SurfaceOutputStandardSpecular&,它们能被用于基于物理的光照模型,从而Unity就具有了PBS(基于物理着色)特性。关于基于物理着色,很多现代引擎都在追求和实现,而真正实现了基于物理着色的引擎才可以被称之为次世代引擎,因为它可以从很大程度上去真实地模拟光线跟踪。Unity使用了美国迪士尼公司所使用的一种称为Cook-Torrance的BRDF模型,再加上全局光照的计算,因此在Unity5当中我们可以把场景和物体渲染得比以前的版本更真实、更漂亮。
  接下来简单了解一下&SurfaceOutput&和&SurfaceOutputStandard&两种结构体的对比。&
struct SurfaceOutput
// diffuse color
// tangent space normal, if written
// specular power in 0..1 range
// specular intensity
// alpha for transparencies
SurfaceOutput
struct SurfaceOutputStandard
// base (diffuse or specular) color
// tangent space normal, if written
// 0=non-metal, 1=metal
// 0=rough, 1=smooth
// occlusion (default 1)
// alpha for transparencies
SurfaceOutputStandard
&  两个结构体都有&Albedo&,但是在Unity5以后,&Albedo&指的是基本的漫反射或者高光的颜色值,而在早期版本中,&Albedo&就只是漫反射颜色值;另外,两者都有&Normal&、&Emission&和&Alpha&值,这些两者都是一样的;但是早期版本有&Specular&,新版已经把它涵盖在&Albedo&当中了;早期版本有&Gloss&,而新版已经用&Smoothness&取代了;新版本有&Metallic&和&Occlusion&,&Metallic&用来显示金属化程度,&Occlusion&是一种剔除特性,这些早期版本都是没有的。
  为了将当前的Surface Shader精简化,我们可以把它改成旧版的Surface Shader。
Shader "Lesson/SurfaceShader2" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
SubShader {
Tags { "RenderType"="Opaque" }
#pragma surface surf Lambert fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainT
struct Input {
float2 uv_MainT
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.
o.Alpha = c.a;
FallBack "Diffuse"
  其中删除了&_Color&、&_Glossiness&和&_Metallic&属性,将&#pragma surface surf Standard fullforwardshadows&改成&#pragma surface surf Lambert fullforwardshadows&。回到场景中,效果如图:
  可以观察到原先球体的金属光泽已经没有,球体仅仅只有漫反射的感觉,这就是早期Surface Shader的效果。
  在当前surface函数当中基本是一些赋值操作,&fixed4 c = tex2D (_MainTex, IN.uv_MainTex);&声明了一个颜色值,&tex2D&是Cg中的一个函数,它主要的功能就是从纹理中进行采样,使用了一个纹理坐标对纹理采样,得到了一个颜色值。然后将采样得到的颜色的rgb值交给了&SurfaceOutput&的&Albedo&值,并将得到颜色的alpha值交给了&SurfaceOutput&的&alpha&值。
  在大概了解了surface函数的意义后,可以考虑将贴图调整成基于灰度来获取alpha通道,但是发现这个物体并没有透明效果,在Surface Shader当中,要达到透明效果,就不能使用先前使用过的alpha混合,启用一个blend选项来实现,因为Surface Shader没有pass通道,但是Unity官方指明了另外的方案,可以在Surface Shader文档的&Optional parameters&找到解决方案,如:
alpha or alpha:auto
alpha:blend
alpha:fade
alpha:premul
alphatest:VariableName
decal:blend
  其中,&alpha or alpha:auto&的功能是会自动选择使用&alpha:fade&或者&alpha:premul&的功能,如果使用的是简单光照函数等同于使用&alpha:fade&,如果使用基于物体的光照函数等同于使用&alpha:premul&左乘运算;&alphatest:VariableName&可以指定一个变量,我们可以用这个变量自身的第四个分量,比如说一个颜色值的alpha值就可以取代真正的"alphatest";&keepalpha&表示保持alpha值;&decal:add&和&decal:blend&都是用来描述印花的方案,其中&decal:add&实际上就是&blend&当中源和目标混合的one和one。  接下来可以先尝试使用&alpha&,将它放在&pragma&附加的指令部分:&#pragma surface surf Lambert fullforwardshadows alpha&。回到工程中,效果如图:
  可以发现球体有了变化,似乎有透明的感觉,但是让人感觉很不自然,上半部分被削了一块,原因是这里没有指定它的渲染序列,需要在Tags当中添加一个渲染序列:&Tags { "RenderType"="Opaque" "queue"="transparent"}&,指定一个&transparent&,表示它是一个透明物体。
  回到场景中,重新给物体选择一下shader,编译通过后可以发现球体已经透明了,效果如图:
  对于一个透明物体,有时并不需要它产生阴影,把阴影选项&fullforwardshadows&删除,回到场景中发现阴影还存在,这是因为shader的&Fallback&里有一个&Diffuse&,如果把&Fallback&注释掉,可以发现场景里球体的阴影消失了。因此,可以发现&Fallback&有一个很好的用途,当Surface Shader编译后没有得到一个阴影投射器时,通过&Fallback&会自动在指定的具有阴影投射器通道的shader中(这里是&Diffuse&)去选择这个阴影投射器使用,这就是&Fallback&的一个妙用。
  如果没有&Fallback&,又希望物体有阴影效果,可以在&pragma&中增加一个阴影投射通道&addshadow&:&#pragma surface surf Lambert alpha addshadow&。回到场景中,发现物体并没有投射阴影,原因是如果一个具有了alpha混合的半透明物体去投射一个阴影是不能得到最终需要的阴影效果的,因为有可能会有其他的物体光线投射并且透过这个物体落到这个物体的阴影范围内,让这个物体去投射一个实体的阴影不符合半透明物体的阴影形象。要产生阴影,需要删除"pragma"的&alpha&选项,取消&transparent&渲染序列,然后回到场景中,就可以到看到阴影效果了,不过这个球体就不透明了。代码以及效果如下:
Shader "Lesson/SurfaceShader2" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
SubShader {
Tags { "RenderType"="Opaque"}
#pragma surface surf Lambert addshadow
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
struct Input {
float2 uv_MainT
sampler2D _MainT
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.
o.Alpha = c.a;
// FallBack "Diffuse"
&&国之画&&&& &&&&chrome插件&&
版权所有 京ICP备号-2
迷上了代码!1106人阅读
Unity Shader(2)
Unity自3.x起,推出了surface shader功能,极大地简化了shader的编写,尤其是光照处理这块。surface shader说白了就是一套代码生成器,最终还是转换为vertex/fragment shader,优点在于隐藏了许多很少会被改动,然而工作量却巨大的细节,例如处理不同光照类型,lightmap,阴影等。开放给开发者的是最多被修改的一些参数,例如颜色,法线等。当然还提供了很多参数和方法,可以让开发者自定义一些功能,例如光照模型,改变顶点等。
这篇文章不是讲解surface shader怎么使用,而是探究unity在背后到底做了些什么?怎么做的?本文接下去都会围绕这两个问题展开阐述。如果是初次接触surface shader,可以看下。另外本文针对的版本是unity 5之前的版本,unity 5加入了基于物理的渲染(PBR),surface shader也加入了对PBR的支持,还没仔细看过,稍后可能会更新到unity 5的内容。
Unity做了些什么
下面就是一个最简单的surface shader
Shader "Custom/Lambert" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
SubShader {
Tags { "RenderType"="Opaque" }
#pragma surface surf Lambert
sampler2D _MainTex
struct Input {
float2 uv_MainTex
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex)
o.Albedo = c.rgb
o.Alpha = c.a
FallBack "Diffuse"
这个shader很简单,定义了surface函数为surf,使用Lambert光照模型,定义了包含uv_MainTex的Input结构体。关于SurfaceOutput和Lambert的定义可以在Lighting.cginc中找到,定义如下:
struct SurfaceOutput {
inline fixed4 LightingLambert (SurfaceOutput s, fixed3 lightDir, fixed atten)
fixed diff = max (0, dot (s.Normal, lightDir));
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
接下去unity做了什么呢?首先他按不同的渲染路径生成代码,一般情况下是和。作为一个移动平台开发者,目前的设备性能还不支持deferred rendering,所以这里只讨论forward rendering的情况。
在forward path下,unity会生成2个pass:forwardbase和forwardadd。
forwardbase是基本的pass,必须得有,他处理场景中最重要的平行光,顶点光照,球谐光照,投射阴影和lightmap。
forwardadd处理额外的像素光照,当光源被设为Important或者QualitySettings里Pixel Light Count里允许的光源数量都将产生一个额外的forwardadd pass来计算光照,将最终的结果以叠加的方式,混合到buffer里去。
具体的流程如下图所示:
看了这张流程图后,对unity在背后做了些什么事情有一个大概的直观的概念。在surface shader中,你只需要在surf函数里描述,你的物体看起来是什么样的(填充SurfaceOutput结构体),以什么样的方式照亮(选择光照模型,unity内置了Lambert和BlinnPhong,当然你也可以自定义,有兴趣的可以戳)。有了这些信息,unity把SurfaceOutput作为输入参数,传入对应的光照方程来计算最终结果。unity在背后把脏活累活都给你做了:为你处理了顶点,法线,视点变换;纹理坐标计算;lightmap的处理;不同光源类型的光照与阴影计算等。
具体的代码如下。看到这茫茫多一坨先别慌,看不懂也没关系,后面我会详细分析每个细节,现在有个大致的印象就可以了。
Shader "Custom/Lambert" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
SubShader {
Tags { "RenderType"="Opaque" }
// ------------------------------------------------------------
// Surface shader code generated out of a CGPROGRAM block:
// ---- forward rendering base pass:
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_fwdbase
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_FORWARDBASE
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal
// Original surface shader snippet:
#line 7 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
//#pragma surface surf Lambert exclude_path:prepass
sampler2D _MainTex
struct Input {
float2 uv_MainTex
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex)
o.Albedo = c.rgb
o.Alpha = c.a
// vertex-to-fragment interpolation data
#ifdef LIGHTMAP_OFF
struct v2f_surf {
float4 pos : SV_POSITION
float2 pack0 : TEXCOORD0
fixed3 normal : TEXCOORD1
fixed3 vlight : TEXCOORD2
LIGHTING_COORDS(3,4)
#ifndef LIGHTMAP_OFF
struct v2f_surf {
float4 pos : SV_POSITION
float2 pack0 : TEXCOORD0
float2 lmap : TEXCOORD1
LIGHTING_COORDS(2,3)
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST
float4 _MainTex_ST
// vertex shader
v2f_surf vert_surf (appdata_full v) {
v2f_surf o
o.pos = mul (UNITY_MATRIX_MVP, v.vertex)
o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex)
#ifndef LIGHTMAP_OFF
o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw
float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL)
#ifdef LIGHTMAP_OFF
o.normal = worldN
// SH/ambient and vertex lights
#ifdef LIGHTMAP_OFF
float3 shlight = ShadeSH9 (float4(worldN,1.0))
o.vlight = shlight
#ifdef VERTEXLIGHT_ON
float3 worldPos = mul(_Object2World, v.vertex).xyz
o.vlight += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, worldPos, worldN )
#endif // VERTEXLIGHT_ON
#endif // LIGHTMAP_OFF
// pass lighting information to pixel shader
TRANSFER_VERTEX_TO_FRAGMENT(o)
#ifndef LIGHTMAP_OFF
sampler2D unity_Lightmap
#ifndef DIRLIGHTMAP_OFF
sampler2D unity_LightmapInd
// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
// prepare and unpack data
#ifdef UNITY_COMPILER_HLSL
Input surfIN = (Input)0
Input surfIN
surfIN.uv_MainTex = IN.pack0.xy
#ifdef UNITY_COMPILER_HLSL
SurfaceOutput o = (SurfaceOutput)0
SurfaceOutput o
o.Albedo = 0.0
o.Emission = 0.0
o.Specular = 0.0
o.Alpha = 0.0
o.Gloss = 0.0
#ifdef LIGHTMAP_OFF
o.Normal = IN.normal
// call surface function
surf (surfIN, o)
// compute lighting & shadowing factor
fixed atten = LIGHT_ATTENUATION(IN)
fixed4 c = 0
// realtime lighting: call lighting function
#ifdef LIGHTMAP_OFF
c = LightingLambert (o, _WorldSpaceLightPos0.xyz, atten)
#endif // LIGHTMAP_OFF || DIRLIGHTMAP_OFF
#ifdef LIGHTMAP_OFF
c.rgb += o.Albedo * IN.vlight
#endif // LIGHTMAP_OFF
// lightmaps:
#ifndef LIGHTMAP_OFF
#ifndef DIRLIGHTMAP_OFF
// directional lightmaps
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy)
fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy)
half3 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 0).rgb
#else // !DIRLIGHTMAP_OFF
// single lightmap
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy)
fixed3 lm = DecodeLightmap (lmtex)
#endif // !DIRLIGHTMAP_OFF
// combine lightmaps with realtime shadows
#ifdef SHADOWS_SCREEN
#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
c.rgb += o.Albedo * min(lm, atten*2)
c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten)
#else // SHADOWS_SCREEN
c.rgb += o.Albedo * lm
#endif // SHADOWS_SCREEN
c.a = o.Alpha
#endif // LIGHTMAP_OFF
// ---- forward rendering additive lights pass:
Name "FORWARD"
Tags { "LightMode" = "ForwardAdd" }
ZWrite Off Blend One One Fog { Color (0,0,0,0) }
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_fwdadd
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_FORWARDADD
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal
// Original surface shader snippet:
#line 7 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
//#pragma surface surf Lambert exclude_path:prepass
sampler2D _MainTex
struct Input {
float2 uv_MainTex
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex)
o.Albedo = c.rgb
o.Alpha = c.a
// vertex-to-fragment interpolation data
struct v2f_surf {
float4 pos : SV_POSITION
float2 pack0 : TEXCOORD0
fixed3 normal : TEXCOORD1
half3 lightDir : TEXCOORD2
LIGHTING_COORDS(3,4)
float4 _MainTex_ST
// vertex shader
v2f_surf vert_surf (appdata_full v) {
v2f_surf o
o.pos = mul (UNITY_MATRIX_MVP, v.vertex)
o.pack0.xy = TRANSFORM_TEX(v.texcoord, _MainTex)
o.normal = mul((float3x3)_Object2World, SCALED_NORMAL)
float3 lightDir = WorldSpaceLightDir( v.vertex )
o.lightDir = lightDir
// pass lighting information to pixel shader
TRANSFER_VERTEX_TO_FRAGMENT(o)
// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
// prepare and unpack data
#ifdef UNITY_COMPILER_HLSL
Input surfIN = (Input)0
Input surfIN
surfIN.uv_MainTex = IN.pack0.xy
#ifdef UNITY_COMPILER_HLSL
SurfaceOutput o = (SurfaceOutput)0
SurfaceOutput o
o.Albedo = 0.0
o.Emission = 0.0
o.Specular = 0.0
o.Alpha = 0.0
o.Gloss = 0.0
o.Normal = IN.normal
// call surface function
surf (surfIN, o)
#ifndef USING_DIRECTIONAL_LIGHT
fixed3 lightDir = normalize(IN.lightDir)
fixed3 lightDir = IN.lightDir
fixed4 c = LightingLambert (o, lightDir, LIGHT_ATTENUATION(IN))
// ---- end of surface shader generated code
FallBack "Diffuse"
想知道unity到底怎么做的,且听下回分解。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:7525次
排名:千里之外
原创:12篇
(2)(1)(1)(1)(1)(6)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'【Unity Shaders】初探Surface Shader背后的机制 - CSDN博客
【Unity Shaders】初探Surface Shader背后的机制
转载请注明出处:写在前面一直以来,Unity Surface Shader背后的机制一直是初学者为之困惑的地方。Unity Surface Shader在Unity 3.0的时候被开放给公众使用,其宣传手段也是号称让所有人都可以轻松地写shader。但由于资料缺乏,很多人知其然不知其所以然,无法理解Unity Surface Shader在背后为我们做了哪些事情。前几天一直被问到一个问题,为什么我的场景里没有灯光,但物体不是全黑的呢?为什么我把Light的颜色调成黑色,物体还是有一些默认颜色呢?这些问题其实都是因为那些物体使用了Surface Shader的缘故。因此,了解Surface Shader背后的机制是非常重要滴~虽然Surface Shader一直是一个神秘的存在,但其实Unity给了我们揭开她面纱的方式:查看它生成的CG代码。大家应该都知道,所谓的Surface Shader实际上是封装了CG语言,隐藏了很多光照处理的细节,它的设计初衷是为了让用户仅仅使用一些指令(#pragma)就可以完成很多事情,并且封装了很多常用的光照模型和函数,例如Lambert、Blinn-Phong等。而查看Surface Shader生成的代码也很简单:在每个编译完成的Surface Shader的面板上,都有个“Show generated code”的按钮,像下面这样:点开后,就可以查看啦~面板上还表明了很多其他的有用信息。而这些方便的功能实际上是Unity 4.5发布出来的。详情可见。使用Surface Shader,很多时候,我们只需要告诉shader,“嘿,使用这些纹理去填充颜色,法线贴图去填充法线,使用Lambert光照模型,其他的不要来烦我!!!”我们不需要考虑是使用forward还是deferred rendering,有多少光源类型、怎样处理这些类型,每个pass需要处理多少个光源!!!(人们总会rant写一个shader是多么的麻烦。。。)So!Unity说,不要急,放着我来~上面的情景当然对于小白是比较简单的方式,Surface Shader可以让初学者快速实现很多常见的shader,例如漫反射、高光反射、法线贴图等,这些常见的效果也都不错。而对应面就是,由于隐藏了很多细节,如果想要自定义一些比较复杂或特殊的效果,使用Surface Shader就无法达到了(或者非常麻烦)。在学了一段时间的Surface Shader后,我认为:如果你从来没有学习过怎样编写shader,而又想写一些常见的、比较简单的shader,那仅学习Surface Shader是一个不错的选择。如果你向往那些高品质的游戏画面,那么Surface Shader是远远无法满足你的,而且某种方面来说它会让你变得越来越困惑。困惑了怎么办呢?老老实实去学习主流的渲染语言吧~比如CG、GLSL、HLSL等。等学了一些上述内容后,再回过头来看Surface Shader就会别有一番理解了。说教了这么多,本篇的主旨其实是分析下Surface Shader背后做的事情啦!也就是,分析Surface Shader到底是怎样解析我们编写的那些surf、LightingXXX等函数的,又是如何得到像素颜色的。那么,开始吧!流水线首先,我们要明白Surface Shader支持哪些特性。详情请见。Surface Shader最重要的部分是两个结构体以及它的编译指令。两个结构体两个结构体就是指struct&Input和SurfaceOutput。其中Input结构体是允许我们自定义的。它可以包含一些纹理坐标和其他提前定义的变量,例如view direction(float3 viewDir)、world space position(worldPos)、world space reflection vector(float3 worldRefl)等。这些变量只有在真正使用的时候才会被计算生成。比如,在某些Pass里生成而某些就生成。另一个结构体是SurfaceOutput。我们无法自定义这个结构体内的变量。关于它最难理解的也就是每个变量的具体含义以及工作机制(对像素颜色的影响)。我们来看一下它的定义(在Lighting.cginc里面):struct SurfaceOutput {
};Albedo:我们通常理解的对光源的反射率。它是通过在Fragment Shader中计算颜色叠加时,和一些变量(如vertex lights)相乘后,叠加到最后的颜色上的。Normal:即其对应的法线方向。只要是受法线影响的计算都会受到影响。Emission:自发光。会在Fragment 最后输出前(调用final函数前,如果定义了的话),使用下面的语句进行简单的颜色叠加:c.rgb += o.ESpecular:高光反射中的指数部分的系数。影响一些高光反射的计算。按目前的理解,也就是在光照模型里会使用到(如果你没有在光照函数等函数——包括Unity内置的光照函数,中使用它,这个变量就算设置了也没用)。有时候,你只在surf函数里设置了它,但也会影响最后的结果。这是因为,你可能使用了Unity内置的光照模型,如BlinnPhong,它会使用如下语句计算高光反射的强度(在Lighting.cginc里):float spec = pow (nh, s.Specular*128.0) * s.GGloss:高光反射中的强度系数。和上面的Specular类似,一般在光照模型里使用。Alpha:通常理解的透明通道。在Fragment Shader中会直接使用下列方式赋值(如果开启了透明通道的话):c.a = o.A上述结论是分析生成的代码所得,若有不对欢迎指出。大家碰到不懂的,也可以像这样分析生成的代码,一般问题都可以理解啦~编译指令编译指令的一般格式如下:#pragma surface surfaceFunction lightModel [optionalparams]Surface Shader和CG其他部分一样,代码也是要写在CGPROGRAM和ENDCG之间。但区别是,它必须写在SubShader内部,而不能写在Pass内部。Surface Shader自己会自动生成所需的各个Pass。由上面的编译格式可以看出,surfaceFunction和lightModel是必须指定的,而且是可选部分。surfaceFunction通常就是名为surf的函数(函数名可以任意),它的函数格式是固定的:void surf (Input IN, inout SurfaceOutput o)即Input是输入,SurfaceOutput是输出。lightModel也是必须指定的。由于Unity内置了一些光照函数——Lambert(diffuse)和Blinn-Phong(specular),因此这里在默认情况下会使用内置的Lambert模型。当然我们也可以自定义。optionalparams包含了很多可用的指令类型,包括开启、关闭一些状态,设置生成的Pass类型,指定可选函数等。这里,我们只关注可指定的函数,其他可去自行查看。除了上述的surfaceFuntion和lightModel,我们还可以自定义两种函数:vertex:VertexFunction和finalcolor:ColorFunction。也就是说,Surface Shader允许我们自定义四种函数。两个结构体+四个函数——它们在整个的render pipeline中的流程如下:从上图可以看出来,Surface Shader背后的”那些女人“就是vertex shader和fragment shader。除了VertexFunction外,另外两个结构体和三个函数都是在fragment shader中扮演了一些角色。Surface Shader首先根据我们的代码生成了很多Pass,用于forwardbase和forwardadd等,这不在本篇的讨论范围。而每个Pass的代码是基于上述四个函数生成的。以一个Pass的代码为例,Surface Shader的生成过程简述如下:直接将CGPROGRAM和ENDCG之间的代码复制过来(其实还是更改了一些编译指令),这些代码包括了我们对Input、surfaceFuntion、LightingXXX等变量和函数的定义。这些函数和变量会在之后的处理过程中当成普通的结构体和函数进行调用,就和在C++中我们会在main函数中调用某些函数一样;分析上述代码,生成v2f_surf结构,用于在Vertex Shader和Fragment Shader之间进行数据传递。Unity会分析我们在四个自定义函数中所使用的变量,例如纹理坐标等。如果需要,它会在v2f_surf中生成相应的变量。而且,即便有时我们在Input中定义了某些变量(如某些纹理坐标),但Unity在分析后续代码时发现我们并没有使用这些变量,那么这些变量实际上是不会在v2f_surf中生成的。这也就是说,Unity做了一些优化动作。生成Vertex Shader。 *&如果我们自定义了VertexFunction,Unity会在这里首先调用VertexFunction修改顶点数据;然后分析VertexFunction修改的数据,最后通过Input结构体将修改结果存储到v2f_surf中。 * 计算v2f_surf中其他默认的变量值。这主要包括了pos、纹理坐标、normal(如果没有使用LightMap)、vlight(如果没有使用LightMap)、lmap(如果使用LightMap)等。 * 最后,通过内置的TRANSFER_VERTEX_TO_FRAGMENT指令将v2f_surf传递给下面的Fragment Shader。生成Fragment Shader。 * 使用v2f_surf中的对应变量填充Input结构,例如一些纹理坐标等。 * 调用surfFuntion填充SurfaceOutput结构。 * 调用LightingXXX函数得到初始的颜色值。 * 进行其他的颜色叠加。如果没有启用LightMap,这里会使用SurfaceOutput.Albedo和v2f_surf.vlight的乘积和原颜色值进行叠加;否则会进行一些更复杂的颜色叠加。 * 最后,如果自定了final函数,则调用它进行最后额颜色修改。代码分析我们以一个Surface Shader为例,分析它生成的代码。Surface Shader如下:Shader &Custom/BasicDiffuse& {
Properties {
_EmissiveColor (&Emissive Color&, Color) = (1,1,1,1)
_AmbientColor
(&Ambient Color&, Color) = (1,1,1,1)
_MySliderValue (&This is a Slider&, Range(0,10)) = 2.5
_RampTex (&Ramp Texture&, 2D) = &white&{}
SubShader {
Tags { &RenderType&=&Opaque& &RenderType&=&Opaque& }
#pragma surface surf BasicDiffuse vertex:vert finalcolor:final noforwardadd
#pragma debug
float4 _EmissiveC
float4 _AmbientC
float _MySliderV
sampler2D _RampT
struct Input
float2 uv_RampT
float4 vertC
void vert(inout appdata_full v, out Input o)
o.vertColor = v.
void surf (Input IN, inout SurfaceOutput o)
pow((_EmissiveColor + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb + tex2D(_RampTex, IN.uv_RampTex).
o.Alpha = c.a;
inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
float difLight = max(0, dot (s.Normal, lightDir));
float hLambert = difLight * 0.5 + 0.5;
float3 ramp = tex2D(_RampTex, float2(hLambert)).
col.rgb = s.Albedo * _LightColor0.rgb * (ramp) *
col.a = s.A
void final(Input IN, SurfaceOutput o, inout fixed4 color) {
color = color * 0.5 + 0.5;
FallBack &Diffuse&
它包含了全部四个函数,以及一些比较常见的运算。为了只关注一个Pass,我添加了noforwardadd指令。它所得到的渲染结果不重要(事实上我只是在BasicDiffuse上瞎改了一些。。。)我们点开查看它生成的代码:Shader &Custom/BasicDiffuse_Gen& {
Properties {
_EmissiveColor (&Emissive Color&, Color) = (1,1,1,1)
_AmbientColor
(&Ambient Color&, Color) = (1,1,1,1)
_MySliderValue (&This is a Slider&, Range(0,10)) = 2.5
_RampTex (&Ramp Texture&, 2D) = &white&{}
SubShader {
Tags { &RenderType&=&Opaque& &RenderType&=&Opaque& }
// ------------------------------------------------------------
// Surface shader code generated out of a CGPROGRAM block:
// ---- forward rendering base pass:
Name &FORWARD&
Tags { &LightMode& = &ForwardBase& }
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_fwdbase nodirlightmap
#include &HLSLSupport.cginc&
#include &UnityShaderVariables.cginc&
#define UNITY_PASS_FORWARDBASE
#include &UnityCG.cginc&
#include &Lighting.cginc&
#include &AutoLight.cginc&
#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal
// Original surface shader snippet:
#line 11 &&
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
//#pragma surface surf BasicDiffuse vertex:vert finalcolor:final noforwardadd
#pragma debug
float4 _EmissiveC
float4 _AmbientC
float _MySliderV
sampler2D _RampT
struct Input
float2 uv_RampT
float4 vertC
void vert(inout appdata_full v, out Input o)
o.vertColor = v.
void surf (Input IN, inout SurfaceOutput o)
pow((_EmissiveColor + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb + tex2D(_RampTex, IN.uv_RampTex).
o.Alpha = c.a;
inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
float difLight = max(0, dot (s.Normal, lightDir));
float hLambert = difLight * 0.5 + 0.5;
float3 ramp = tex2D(_RampTex, float2(hLambert)).
col.rgb = s.Albedo * _LightColor0.rgb * (ramp);
col.a = s.A
void final(Input IN, SurfaceOutput o, inout fixed4 color) {
color = color * 0.5 + 0.5;
// vertex-to-fragment interpolation data
#ifdef LIGHTMAP_OFF
struct v2f_surf {
float4 pos : SV_POSITION;
float2 pack0 : TEXCOORD0;
float4 cust_vertColor : TEXCOORD1;
fixed3 normal : TEXCOORD2;
fixed3 vlight : TEXCOORD3;
// LIGHTING_COORDS在AutoLight.cginc里定义
// 本质上就是一个#define指令
// #define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
// #define SHADOW_COORDS(idx1) float3 _ShadowCoord : TEXCOORD##idx1;
LIGHTING_COORDS(4,5)
#ifndef LIGHTMAP_OFF
struct v2f_surf {
float4 pos : SV_POSITION;
float2 pack0 : TEXCOORD0;
float4 cust_vertColor : TEXCOORD1;
float2 lmap : TEXCOORD2;
LIGHTING_COORDS(3,4)
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
定义所需的纹理坐标
float4 _RampTex_ST;
// vertex shader
v2f_surf vert_surf (appdata_full v) {
使用自定义的vert函数填充Input结构
Input customInputD
vert (v, customInputData);
再赋值给真正所需的v2f_surf结构
o.cust_vertColor = customInputData.vertC
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
将顶点的纹理坐标转换到纹理对应坐标
o.pack0.xy = TRANSFORM_TEX(v.texcoord, _RampTex);
#ifndef LIGHTMAP_OFF
如果启用了LightMap,则计算对应的LightMap坐标
o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.
计算世界坐标系中法线的方向
SCALED_NORMAL在UnityCG.cginc里定义
// 本质上就是一个#define指令
// #define SCALED_NORMAL (v.normal * unity_Scale.w)
float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
如果没有开启LightMap,
// 顶点法线方向就是worldN
#ifdef LIGHTMAP_OFF
o.normal = worldN;
// SH/ambient and vertex lights
#ifdef LIGHTMAP_OFF
如果没有开启LightMap,
vertex lights就是球面调和函数的结果
球面调和函数ShadeSH9在UnityCG.cginc里定义
float3 shlight = ShadeSH9 (float4(worldN,1.0));
o.vlight =
// unity_4LightPosX0等变量在UnityShaderVariables.cginc里定义
#ifdef VERTEXLIGHT_ON
float3 worldPos = mul(_Object2World, v.vertex).
o.vlight += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, worldPos, worldN );
#endif // VERTEXLIGHT_ON
#endif // LIGHTMAP_OFF
// pass lighting information to pixel shader
// TRANSFER_VERTEX_TO_FRAGMENT在AutoLight.cginc里定义,
// 本质上就是一个#define指令
// 用于转换v2f_surf中的_LightCoord和_ShadowCoord
TRANSFER_VERTEX_TO_FRAGMENT(o);
#ifndef LIGHTMAP_OFF
sampler2D unity_L
#ifndef DIRLIGHTMAP_OFF
sampler2D unity_LightmapI
// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
// prepare and unpack data
#ifdef UNITY_COMPILER_HLSL
Input surfIN = (Input)0;
Input surfIN;
使用v2f_surf中的变量给Input中的纹理坐标进行赋值
surfIN.uv_RampTex = IN.pack0.
surfIN.vertColor = IN.cust_vertC
#ifdef UNITY_COMPILER_HLSL
SurfaceOutput o = (SurfaceOutput)0;
// 初始化SurfaceOutput结构
o.Albedo = 0.0;
o.Emission = 0.0;
o.Specular = 0.0;
o.Alpha = 0.0;
o.Gloss = 0.0;
#ifdef LIGHTMAP_OFF
o.Normal = IN.
// call surface function
调用自定义的surf函数填充SurfaceOutput结构
surf (surfIN, o);
// compute lighting & shadowing factor
// LIGHT_ATTENUATION在AutoLight.cginc里定义,
// 本质上就是一个#define指令
// 用于计算光衰减
fixed atten = LIGHT_ATTENUATION(IN);
fixed4 c = 0;
// realtime lighting: call lighting function
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// 调用自定义的LightXXX函数,
// 使用填充好的SurfaceOutput等变量作为参数,
// 得到初始的像素值
c = LightingBasicDiffuse (o, _WorldSpaceLightPos0.xyz, atten);
#endif // LIGHTMAP_OFF || DIRLIGHTMAP_OFF
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// 向像素叠加vertex light的光照颜色
c.rgb += o.Albedo * IN.
#endif // LIGHTMAP_OFF
// lightmaps:
#ifndef LIGHTMAP_OFF
// 计算LightMap,这部分不懂
#ifndef DIRLIGHTMAP_OFF
// directional lightmaps
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
half3 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 0).
#else // !DIRLIGHTMAP_OFF
// single lightmap
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
fixed3 lm = DecodeLightmap (lmtex);
#endif // !DIRLIGHTMAP_OFF
// combine lightmaps with realtime shadows
#ifdef SHADOWS_SCREEN
#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
c.rgb += o.Albedo * min(lm, atten*2);
c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten);
#else // SHADOWS_SCREEN
c.rgb += o.Albedo *
#endif // SHADOWS_SCREEN
// 给Alpha通道赋值
#endif // LIGHTMAP_OFF
// 调用自定义的final函数,
// 对像素值进行最后的更改
final (surfIN, o, c);
// ---- end of surface shader generated code
FallBack &Diffuse&
其中比较重要的部分我都写了注释。一些问题回到我们一开始的那个问题:为什么我的场景里没有灯光,但物体不是全黑的呢?这一切都是Fragment Shader中一些颜色叠加计算的结果。我们仔细观察Fragment Shader中计算颜色的部分。前面说过,它使用LightingXXX对颜色值进行初始化,但后面还进行了一系列颜色叠加计算。其中,在没有使用LightMap的情况下,Unity还计算了vertex lights对颜色的影响,也就是下面这句话:
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// 向像素叠加vertex light的光照颜色
c.rgb += o.Albedo * IN.
#endif // LIGHTMAP_OFF而IN.vlight是在Vertex Shader中计算的:
如果没有开启LightMap,
vertex lights就是球面调和函数的结果
球面调和函数ShadeSH9在UnityCG.cginc里定义
float3 shlight = ShadeSH9 (float4(worldN,1.0));
o.vlight =我们可以去查看ShadeSH9函数的实现:// normal should be normalized, w=1.0
half3 ShadeSH9 (half4 normal)
half3 x1, x2, x3;
// Linear + constant polynomial terms
x1.r = dot(unity_SHAr,normal);
x1.g = dot(unity_SHAg,normal);
x1.b = dot(unity_SHAb,normal);
// 4 of the quadratic polynomials
half4 vB = normal.xyzz * normal.
x2.r = dot(unity_SHBr,vB);
x2.g = dot(unity_SHBg,vB);
x2.b = dot(unity_SHBb,vB);
// Final quadratic polynomial
float vC = normal.x*normal.x - normal.y*normal.y;
x3 = unity_SHC.rgb * vC;
return x1 + x2 + x3;
} 它是一个球面调和函数,但unity_SHAr这些变量具体是什么我还不清楚。。。如果有人知道麻烦告诉我一下,不胜感激~但是,这些变量是和Unity使用了一个全局环境光(你可以在Edit-&RenderSettings-&Ambient Light中调整)有关。如果把这个环境光也调成黑色,那么场景就真的全黑了。呼呼呼,关于这些光源计算的部分就是一开始所说的那些让写shader变得很复杂的原因之一!如果你真的去看那些在UnityCG.cginc、AutoLight.cginc等文件里的关于指令的定义,可以发现Unity是根据定义的光照类型来处理不同的光照的。这部分还没有搞明白,后面会继续探究一下的!上述内容纯属研究所得,如有错误欢迎指正~
本文已收录于以下专栏:
相关文章推荐
Vertex and Fragment Shader与Surface Shade的一些常见性质
上一篇中,我们演示了如何使用自定义的光照模型进行渲染。这一次,我们将进一步看一下怎样对它做一些变化来得到更好的效果!
我们会列出两种方法:使用Half Lambert lighting model(半...
之前按照别人的代码,出问题了。其实还是对unity shader的理解不够深。下面是遇到的一个关于surfaceouput的弱鸡问题。
struct SurfaceOutput {    
    h...
文档地址: http://vertx.io/docs/vertx-core/java/Verticlesverticles是一个很重要的概念。你可以将verticles当做是Actor Model(一...
在vert函数中加入一句UNITY_INITIALIZE_OUTPUT(Input,参数名);即可
[cpp] view
void vert ...
对于Unity中的表面着色器(Surface Shader),它的代码整体结构如下所示:
     Shader &name& {
         Properties {
  &#1...
最近在从零开始写一个移动端的AR系统,坑实在是太多了!!!整个项目使用了OpenCV第三方库,但对于摄像机来说,和原生Camera的方法基本相同。
以OpenCV的...
好久没有更新博客!感觉又荒废了shader的学习,吼吼,还是得加油啊!
这次的内容是打包合并textures。
Textures不仅仅可以用来存储颜色信息,还可以存储很多数据信息。这些...
他的最新文章
讲师:吴岸城
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 surfaceshader 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信