Simple cg shader, confused with Lightmode and built-in vars

Hi,

I went through the different examples of Nvidia Cg Tutorial and I’m trying to implement basic vertex diffuse lighting, for learning purposes.

Here’s the code :

Shader "Debug/SimpleVertex" {
  Properties { 
  _Kd ("Diffuse",Color) = (0,1,0,0)
  } 
  
  SubShader {
    Pass {
		//Name "Lighting On"
		Tags { "LightMode" = "Pixel" }
		
		CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
		#include "UnityCG.cginc"
		
		float4 _Kd;

        void vert(float4 pos  : POSITION, float4 normal : NORMAL,
			out float4 oPos : POSITION, out float4 ocolor : COLOR) {
		
		oPos = mul(glstate.matrix.mvp, pos);
		
		float3 P = pos.xyz;
		float3 N = normal;
		
		float3 L = normalize(_ObjectSpaceLightPos0 - P);		
		//float3 L = normalize(glstate.light[0].position - P);
		
		float diffuseLight = max(dot(N, L), 0);
		float4 diffuse = _Kd * glstate.light[0].diffuse * diffuseLight;
		
		ocolor.xyz = diffuse;
		ocolor.w = 1;

        }
       
        void frag(float4 color : COLOR, out float4 ocolor : COLOR) {
			ocolor = color;
			
        }
      ENDCG
    }
  }
}

It kinda works, but there are things I don’t understand.
1/ there’s a sphere and a plane (unity primitives) and a point light in my scene. If I scale the plane in any direction just a bit, it becomes black. The same happens with a cube, cylinder or capsule, but not with the sphere, which I can scale.

2/ the light is in auto render mode. If I move an object outside the range of the point light it dissapears (there’s still the wireframe but it’s fully transparent).

Docs say :
Pixel: Rendered once for each pixel light.
So that’s fine.

but if I switch to PixelOrNone, the objects are transparent when in range of the light and black when outside of the range. Even if the light is in “force pixel” render mode.
Docs say : PixelOrNone: Rendered if only pixel lights or no lights at all hit the object. Often used as an ambient pass.
So I would have guessed the objects get rendered when in range and black outside.

Further tests : PixelOnly: same result than Pixel. VertexAndPixel : objects transparent all the time.

Vertex and VertexOrPixel and VertexOrNone and VertexOnly: the lighting of the object is freezed and not affected by the light position, and when objects are out of range they become transparent.

The docs say : Vertex lights are rendered using “vertex” passes . All lights are rendered at once, using a fixed function OpenGL/Direct3D lighting model (Blinn-Phong). It is not possible to use vertex programs with vertex lights.

So the vertex and fragment program have no effect when in pixel mode, don’t really understand what I get for “VertexOrPixel” mode though.

3/ has the “Lighting On” line any effect when using cg code ?

4/ I tried using glstate.light[0].position but the shading was wrong, so I guess the position is not in world space. So I use _ObjectSpaceLightPos0 which I found in UnityCG.cginc, but there are no variables for the other lights and I’m not sure which matrix I should use to get glstate.light[1].position in the correct space.

Sorry for the lengthy post, thanks if you can clear things up.
I know I’m supposed to use the unity shaderlab stuff, but I’d like to understand the basics to build more complicated shader.

Thanks a lot
Vincent

I recently delved into this, the shader code there could help you.

  1. I’m not quite sure whats happening there but if I remember my tests right, _ObjectSpaceLightPos0 only really works right in the frag shader. Scaling might be doing something to the transform matrix that _ObjectSpaceLightPos0 might not account for.

If you want to light from the vertex program use glstate.light[0-3]. To translate the world position of the glstate light, multiply it by glstate.matrix.invtrans.modelview[0] like in the shader.

  1. My explanation for this might be a bit fuzzy, but I’ll try to be as clear as I can.

Auto mode: When your light is out of range of the object, unity might not bother to fill out _ObjectSpaceLightPos0 to save processing time so your shader would fail and not return anything which would result in a transparent object.

PixelOrNone: I’m not really sure whats happening there. My expectation would be similar to yours but yeah…

Vertex: It’s possible to use vertex lights with vertex programs but you’ll have to calculate the lighting yourself.

  1. As far as I can tell no.

  2. See answer in 1 for this :slight_smile:

Thanks a lot for this answer and your other thread. I’ve spent some more time with shaderlab and understand its syntax better also.

Some more findings of mine :
for 1/ the problem goes away if I use the appdata struct, defined in UnityCG.cginc in the parameters of the vertex function, so that’s very strange.

So _ObjectSpaceLightPos0 works both in vertex and fragment shader (I had the same problem in a simple fragment shader test)

For the lightmode, if I add Blend AppSrcAdd AppDstAdd and an ambient pass like in the builtin shaders, if Lightmode is None instead or PixelOrNone in the ambient pass, only the last light is taken into account in the illumination, so that’s really strange too.

How would you do that ? if lightmode is set to vertex, the cg shaders are not taken into account in my tests, only the

Lighting On
Material {
Diffuse [_Kd]
}

syntax is (that’s what the “lighting On” is for, by the way).

I quickly tried your shader from the other thread, but it gives me lots of compile error.
But I think that summing the lights contribution in the vertex shader as you do would result in the lights being counted too many times, since unity executes the shader for the number of lights affecting the object.

Given that the fixed function lighting does eight lights and supports very old hardware, implementing vertex lighting in the programmable pipeline (where more than two lights seem to require SM3) seems like a lot of work for reduced performance, features and compatibility. I’d really recommend trying to implement per-pixel lighting instead, which is something you can probably do better than the Unity built-in shaders.