查看“︁GLSL Programming/Unity/Diffuse Reflection”︁的源代码
←
GLSL Programming/Unity/Diffuse Reflection
跳转到导航
跳转到搜索
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[File:The_phase_8_day_of_the_moon.jpg|250px|thumb|left|The light reflection from the surface of the moon is (in a good approximation) only diffuse.]] This tutorial covers '''per-vertex diffuse reflection'''. It's the first in a series of tutorials about basic lighting in Unity. In this tutorial, we start with diffuse reflection from a single directional light source and then include point light sources and multiple light sources (using multiple passes). Further tutorials cover extensions of this, in particular specular reflection, per-pixel lighting, and two-sided lighting. [[File:Diffuse_Reflection_Vectors.svg|250px|thumb|left|Diffuse reflection can be computed using the surface normal vector N and the light vector L, i.e. the vector to the light source.]] ===Diffuse Reflection=== The moon exhibits almost exclusively diffuse reflection (also called Lambertian reflection), i.e. light is reflected into all directions without specular highlights. Other examples of such materials are chalk and matte paper; in fact, any surface that appears dull and matte. In the case of perfect diffuse reflection, the intensity of the observed reflected light depends on the cosine of the angle between the surface normal vector and the ray of the incoming light. As illustrated in the figure {{Hide in print|to the left}}{{Only in print|below}}, it is common to consider normalized vectors starting in the point of a surface, where the lighting should be computed: the normalized surface normal vector '''N''' is orthogonal to the surface and the normalized light direction '''L''' points to the light source. For the observed diffuse reflected light <math>I_\text{diffuse}</math>, we need the cosine of the angle between the normalized surface normal vector '''N''' and the normalized direction to the light source '''L''', which is the dot product '''N'''·'''L''' because the dot product '''a'''·'''b''' of any two vectors '''a''' and '''b''' is: <math>\mathbf{a} \cdot \mathbf{b} = \left\vert \mathbf{a}\right\vert \left\vert \mathbf{b}\right\vert \cos \measuredangle(\mathbf{a},\mathbf{b})</math>. In the case of normalized vectors, the lengths |'''a'''| and |'''b'''| are both 1. If the dot product '''N'''·'''L''' is negative, the light source is on the “wrong” side of the surface and we should set the reflection to 0. This can be achieved by using max(0, '''N'''·'''L'''), which makes sure that the value of the dot product is clamped to 0 for negative dot products. Furthermore, the reflected light depends on the intensity of the incoming light <math>I_\text{incoming}</math> and a material constant <math>k_\text{diffuse}</math> for the diffuse reflection: for a black surface, the material constant <math>k_\text{diffuse}</math> is 0, for a white surface it is 1. The equation for the diffuse reflected intensity is then: <math>I_\text{diffuse} = I_\text{incoming}\,k_\text{diffuse} \max(0,\mathbf{N}\cdot \mathbf{L})</math> For colored light, this equation applies to each color component (e.g. red, green, and blue). Thus, if the variables <math>I_\text{diffuse}</math>, <math>I_\text{incoming}</math>, and <math>k_\text{diffuse}</math> denote color vectors and the multiplications are performed component-wise (which they are for vectors in GLSL), this equation also applies to colored light. This is what we actually use in the shader code. ===Shader Code for One Directional Light Source=== If we have only one directional light source, the shader code for implementing the equation for <math>I_\text{diffuse}</math> is relatively small. In order to implement the equation, we follow the questions about implementing equations, which were discussed in {{GLSL Programming Unity SectionRef|Silhouette Enhancement}}: * Should the equation be implemented in the vertex shader or the fragment shader? We try the vertex shader here. In {{GLSL Programming Unity SectionRef|Smooth Specular Highlights}}, we will look at an implementation in the fragment shader. * In which coordinate system should the equation be implemented? We try world space by default in Unity. (Which turns out to be a good choice here because Unity provides the light direction in world space.) * Where do we get the parameters from? The answer to this is a bit longer: We use a shader property (see {{GLSL Programming Unity SectionRef|Shading in World Space}}) to let the user specify the diffuse material color <math>k_\text{diffuse}</math>. We can get the direction to the light source in world space from the Unity-specific uniform <code>_WorldSpaceLightPos0</code> and the light color <math>I_\text{incoming}</math> from the Unity-specific uniform <code>_LightColor0</code>. As mentioned in {{GLSL Programming Unity SectionRef|Shading in World Space}}, we have to tag the shader pass with <code>Tags {"LightMode" = "ForwardBase"}</code> to make sure that these uniforms have the correct values. (Below we will discuss what this tag actually means.) We get the surface normal vector in object coordinates from the attribute <code>gl_Normal</code>. Since we implement the equation in world space, we have to convert the surface normal vector from object space to world space as discussed in {{GLSL Programming Unity SectionRef|Silhouette Enhancement}}. The shader code then looks like this: <syntaxhighlight lang="GLSL"> Shader "GLSL per-vertex diffuse lighting" { Properties { _Color ("Diffuse Material Color", Color) = (1,1,1,1) } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } // make sure that all uniforms are correctly set GLSLPROGRAM uniform vec4 _Color; // shader property specified by users // The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniform mat4 _Object2World; // model matrix uniform mat4 _World2Object; // inverse model matrix uniform vec4 _WorldSpaceLightPos0; // direction to or position of light source uniform vec4 _LightColor0; // color of light source (from "Lighting.cginc") varying vec4 color; // the diffuse lighting computed in the vertex shader #ifdef VERTEX void main() { mat4 modelMatrix = _Object2World; mat4 modelMatrixInverse = _World2Object; // unity_Scale.w // is unnecessary because we normalize vectors vec3 normalDirection = normalize( vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse)); vec3 lightDirection = normalize( vec3(_WorldSpaceLightPos0)); vec3 diffuseReflection = vec3(_LightColor0) * vec3(_Color) * max(0.0, dot(normalDirection, lightDirection)); color = vec4(diffuseReflection, 1.0); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } #endif #ifdef FRAGMENT void main() { gl_FragColor = color; } #endif ENDGLSL } } // The definition of a fallback shader should be commented out // during development: // Fallback "Diffuse" } </syntaxhighlight> When you use this shader, make sure that there is only one light source in the scene, which has to be directional. If there is no light source, you can create a directional light source by selecting '''Game Object > Create Other > Directional Light''' from the main menu. Also, make sure that the “Forward Rendering Path” is active by selecting '''Edit > Project Settings > Player''' and then in the '''Inspector View''' the '''Per-Platform Settings > Other Settings > Rendering > Rendering Path''' should be set to '''Forward'''. (See below for more details about the “Forward Rendering Path”.) ===Fallback Shaders=== The line <code>Fallback "Diffuse"</code> in the shader code defines a built-in fallback shader in case Unity doesn't find an appropriate subshader. For our example, Unity would use the fallback shader if it doesn't use the “forward rendering path” (see below) or if it couldn't compile the shader code. By choosing the specific name “_Color” for our shader property, we make sure that this built-in fallback shader can also access it. The source code of the built-in shaders is available at [http://unity3d.com/support/resources/assets/built-in-shaders Unity's website]. Inspection of this source code appears to be the only way to determine a suitable fallback shader and the names of the properties that it is using. As mentioned, Unity will also use the fallback shader if there is a compile error in the shader code. In this case, the error is only be reported in the '''Inspector View''' of the shader; thus, it might be difficult to understand that the fallback shader is being used. Therefore, it is usually a good idea to comment the fallback instruction out during development of a shader but include it in the final version for better compatibility. ===Shader Code for Multiple Directional (Pixel) Lights=== So far, we have only considered a single light source. In order to handle multiple light sources, Unity chooses various techniques depending on the rendering and quality settings. In the tutorials here, we will only cover the “Forward Rendering Path”. In order to choose it, select '''Edit > Project Settings > Player''' and then in the Inspector View set '''Per-Platform Settings > Other Settings > Rendering > Rendering Path''' to '''Forward'''. (Moreover, all cameras should be configured to use the player settings, which they are by default.) In this tutorial we consider only Unity's so-called '''pixel lights'''. For the first pixel light (which always is a directional light), Unity calls the shader pass tagged with <code>Tags { "LightMode" = "ForwardBase" }</code> (as in our code above). For each additional pixel light, Unity calls the shader pass tagged with <code>Tags { "LightMode" = "ForwardAdd" }</code>. In order to make sure that all lights are rendered as pixel lights, you have to make sure that the quality settings allow for enough pixel lights: Select '''Edit > Project Settings > Quality''' and then increase the number labeled '''Pixel Light Count''' in any of the quality settings that you use. If there are more light sources in the scene than pixel light count allows for, Unity renders only the most important lights as pixel lights. Alternatively, you can set the '''Render Mode''' of all light sources to '''Important''' in order to render them as pixel lights. (See {{GLSL Programming Unity SectionRef|Multiple Lights}} for a discussion of the less important '''vertex lights'''.) Our shader code so far is OK for the <code>ForwardBase</code> pass. For the <code>ForwardAdd</code> pass, we need to add the reflected light to the light that is already stored in the framebuffer. To this end, we just have to configure the blending to add the new fragment color (<code>gl_FragColor</code>) to the color in the framebuffer. As discussed in {{GLSL Programming Unity SectionRef|Transparency}}, this is achieved by an additive blend equation, which is specified by this line: <code> Blend One One</code> Blending automatically clamps all results between 0 and 1; thus, we don't have to worry about colors or alpha values greater than 1. All in all, our new shader for multiple directional lights becomes: <syntaxhighlight lang="GLSL"> Shader "GLSL per-vertex diffuse lighting" { Properties { _Color ("Diffuse Material Color", Color) = (1,1,1,1) } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } // pass for first light source GLSLPROGRAM uniform vec4 _Color; // shader property specified by users // The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniform mat4 _Object2World; // model matrix uniform mat4 _World2Object; // inverse model matrix uniform vec4 _WorldSpaceLightPos0; // direction to or position of light source uniform vec4 _LightColor0; // color of light source (from "Lighting.cginc") varying vec4 color; // the diffuse lighting computed in the vertex shader #ifdef VERTEX void main() { mat4 modelMatrix = _Object2World; mat4 modelMatrixInverse = _World2Object; // unity_Scale.w // is unnecessary because we normalize vectors vec3 normalDirection = normalize( vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse)); vec3 lightDirection = normalize( vec3(_WorldSpaceLightPos0)); vec3 diffuseReflection = vec3(_LightColor0) * vec3(_Color) * max(0.0, dot(normalDirection, lightDirection)); color = vec4(diffuseReflection, 1.0); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } #endif #ifdef FRAGMENT void main() { gl_FragColor = color; } #endif ENDGLSL } Pass { Tags { "LightMode" = "ForwardAdd" } // pass for additional light sources Blend One One // additive blending GLSLPROGRAM uniform vec4 _Color; // shader property specified by users // The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniform mat4 _Object2World; // model matrix uniform mat4 _World2Object; // inverse model matrix uniform vec4 _WorldSpaceLightPos0; // direction to or position of light source uniform vec4 _LightColor0; // color of light source (from "Lighting.cginc") varying vec4 color; // the diffuse lighting computed in the vertex shader #ifdef VERTEX void main() { mat4 modelMatrix = _Object2World; mat4 modelMatrixInverse = _World2Object; // unity_Scale.w // is unnecessary because we normalize vectors vec3 normalDirection = normalize( vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse)); vec3 lightDirection = normalize( vec3(_WorldSpaceLightPos0)); vec3 diffuseReflection = vec3(_LightColor0) * vec3(_Color) * max(0.0, dot(normalDirection, lightDirection)); color = vec4(diffuseReflection, 1.0); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } #endif #ifdef FRAGMENT void main() { gl_FragColor = color; } #endif ENDGLSL } } // The definition of a fallback shader should be commented out // during development: // Fallback "Diffuse" } </syntaxhighlight> This appears to be a rather long shader; however, both passes are identical apart from the tag and the <code>Blend</code> setting in the <code>ForwardAdd</code> pass. ===Changes for a Point Light Source=== In the case of a directional light source <code>_WorldSpaceLightPos0</code> specifies the direction from where light is coming. In the case of a point light source (or a spot light source), however, <code>_WorldSpaceLightPos0</code> specifies the position of the light source in world space and we have to compute the direction to the light source as the difference vector from the position of the vertex in world space to the position of the light source. Since the 4th coordinate of a point is 1 and the 4th coordinate of a direction is 0, we can easily distinguish between the two cases: <syntaxhighlight lang="GLSL"> vec3 lightDirection; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { lightDirection = normalize(vec3(_WorldSpaceLightPos0)); } else // point or spot light { lightDirection = normalize( vec3(_WorldSpaceLightPos0 - modelMatrix * gl_Vertex)); } </syntaxhighlight> While there is no attenuation of light for directional light sources, we should add some attenuation with distance to point and spot light source. As light spreads out from a point in three dimensions, it's covering ever larger virtual spheres at larger distances. Since the surface of these spheres increases quadratically with increasing radius and the total amount of light per sphere is the same, the amount of light per area decreases quadratically with increasing distance from the point light source. Thus, we should divide the intensity of the light source by the squared distance to the vertex. Since a quadratic attenuation is rather rapid, we use a linear attenuation with distance, i.e. we divide the intensity by the distance instead of the squared distance. The code could be: <syntaxhighlight lang="GLSL"> vec3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(vec3(_WorldSpaceLightPos0)); } else // point or spot light { vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 - modelMatrix * gl_Vertex); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } </syntaxhighlight> The factor <code>attenuation</code> should then be multiplied with <code>_LightColor0</code> to compute the incoming light; see the shader code below. Note that spot light sources have additional features, which are beyond the scope of this tutorial. Also note that this code is unlikely to give you the best performance because any <code>if</code> is usually quite costly. Since <code>_WorldSpaceLightPos0.w</code> is either 0 or 1, it is actually not too hard to rewrite the code to avoid the use of <code>if</code> and optimize a bit further: <syntaxhighlight lang="GLSL"> vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 - modelMatrix * gl_Vertex * _WorldSpaceLightPos0.w); float one_over_distance = 1.0 / length(vertexToLightSource); float attenuation = mix(1.0, one_over_distance, _WorldSpaceLightPos0.w); vec3 lightDirection = vertexToLightSource * one_over_distance; </syntaxhighlight> However, we will use the version with <code>if</code> for clarity. (“Keep it simple, stupid!”) The complete shader code for multiple directional and point lights is: <syntaxhighlight lang="GLSL"> Shader "GLSL per-vertex diffuse lighting" { Properties { _Color ("Diffuse Material Color", Color) = (1,1,1,1) } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } // pass for first light source GLSLPROGRAM uniform vec4 _Color; // shader property specified by users // The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniform mat4 _Object2World; // model matrix uniform mat4 _World2Object; // inverse model matrix uniform vec4 _WorldSpaceLightPos0; // direction to or position of light source uniform vec4 _LightColor0; // color of light source (from "Lighting.cginc") varying vec4 color; // the diffuse lighting computed in the vertex shader #ifdef VERTEX void main() { mat4 modelMatrix = _Object2World; mat4 modelMatrixInverse = _World2Object; // unity_Scale.w // is unnecessary because we normalize vectors vec3 normalDirection = normalize( vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse)); vec3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(vec3(_WorldSpaceLightPos0)); } else // point or spot light { vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 - modelMatrix * gl_Vertex); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } vec3 diffuseReflection = attenuation * vec3(_LightColor0) * vec3(_Color) * max(0.0, dot(normalDirection, lightDirection)); color = vec4(diffuseReflection, 1.0); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } #endif #ifdef FRAGMENT void main() { gl_FragColor = color; } #endif ENDGLSL } Pass { Tags { "LightMode" = "ForwardAdd" } // pass for additional light sources Blend One One // additive blending GLSLPROGRAM uniform vec4 _Color; // shader property specified by users // The following built-in uniforms (except _LightColor0) // are also defined in "UnityCG.glslinc", // i.e. one could #include "UnityCG.glslinc" uniform mat4 _Object2World; // model matrix uniform mat4 _World2Object; // inverse model matrix uniform vec4 _WorldSpaceLightPos0; // direction to or position of light source uniform vec4 _LightColor0; // color of light source (from "Lighting.cginc") varying vec4 color; // the diffuse lighting computed in the vertex shader #ifdef VERTEX void main() { mat4 modelMatrix = _Object2World; mat4 modelMatrixInverse = _World2Object; // unity_Scale.w // is unnecessary because we normalize vectors vec3 normalDirection = normalize( vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse)); vec3 lightDirection; float attenuation; if (0.0 == _WorldSpaceLightPos0.w) // directional light? { attenuation = 1.0; // no attenuation lightDirection = normalize(vec3(_WorldSpaceLightPos0)); } else // point or spot light { vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 - modelMatrix * gl_Vertex); float distance = length(vertexToLightSource); attenuation = 1.0 / distance; // linear attenuation lightDirection = normalize(vertexToLightSource); } vec3 diffuseReflection = attenuation * vec3(_LightColor0) * vec3(_Color) * max(0.0, dot(normalDirection, lightDirection)); color = vec4(diffuseReflection, 1.0); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } #endif #ifdef FRAGMENT void main() { gl_FragColor = color; } #endif ENDGLSL } } // The definition of a fallback shader should be commented out // during development: // Fallback "Diffuse" } </syntaxhighlight> Note that the light source in the <code>ForwardBase</code> pass always is a directional light; thus, the code for the first pass could actually be simplified. On the other hand, using the same GLSL code for both passes, makes it easier to copy & paste the code from one pass to the other in case we have to edit the shader code. If there is a problem with the shader, remember to activate the “Forward Rendering Path” by selecting '''Edit > Project Settings > Player''' and then in the '''Inspector View''' set '''Per-Platform Settings > Other Settings > Rendering > Rendering Path''' to '''Forward'''. ===Changes for a Spotlight=== Unity implements spotlights with the help of cookie textures as described in {{GLSL Programming Unity SectionRef|Cookies}}; however, this is somewhat advanced. Here, we treat spotlights as if they were point lights. ===Summary=== Congratulations! You just learned how Unity's per-pixel lights work. This is essential for the following tutorials about more advanced lighting. We have also seen: *What diffuse reflection is and how to describe it mathematically. *How to implement diffuse reflection for a single directional light source in a shader. *How to extend the shader for point light sources with linear attenuation. *How to further extend the shader to handle multiple per-pixel lights. ===Further Reading=== If you still want to know more * about transforming normal vectors into world space, you should read {{GLSL Programming Unity SectionRef|Shading in World Space}}. * about uniform variables provided by Unity and shader properties, you should read {{GLSL Programming Unity SectionRef|Shading in World Space}}. * about (additive) blending, you should read {{GLSL Programming Unity SectionRef|Transparency}}. * about pass tags in Unity (e.g. <code>ForwardBase</code> or <code>ForwardAdd</code>), you should read [http://unity3d.com/support/documentation/Components/SL-PassTags.html Unity's ShaderLab reference about pass tags]. * about how Unity processes light sources in general, you should read [http://unity3d.com/support/documentation/Manual/RenderingPaths.html Unity's manual about rendering paths].
该页面使用的模板:
Template:GLSL Programming Unity SectionRef
(
查看源代码
)
Template:Hide in print
(
查看源代码
)
Template:Only in print
(
查看源代码
)
返回
GLSL Programming/Unity/Diffuse Reflection
。
导航菜单
个人工具
登录
命名空间
页面
讨论
不转换
查看
阅读
查看源代码
查看历史
更多
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
工具
链入页面
相关更改
页面信息