- Home /
Blinn-Phong shader (vertex lit) from scratch.
Hey all, I have recently started messing around with shaders (GLSL, CG). I know that Unity has the awesome ShaderLab, which is aimed to get us started on shader development and hit the ground running. What I wanted to do was write a shader from scratch in Cg, without any of the help that ShaderLab provides, to help me get a better understanding of the stuff that goes into writing more complex vertex and fragment shaders.
So I am starting by implementing the most basic vertex lit Blinn-Phong shader. Here's the shader code..
Shader "CgShaders/PerVertexBasicLightingShader" {
Properties {
_Ka ("Ka", Color) = (1, 1, 1, 1)
_Kd ("Kd", Color) = (1, 1, 1, 1)
_Ks ("Ks", Color) = (1, 1, 1, 1)
_Shininess("Shininess", Range(0, 1)) = 0.5
_LightColor ("Light Color", Color) = (1, 1, 1, 1)
_LightPosition ("Light Position", Vector) = (0, 0, 0, 0)
_EyePosition ("Eye Position", Vector) = (0, 0, 0, 0)
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"
//material properties
half4 _Ka, _Kd, _Ks;
float _Shininess;
//light properties
half4 _LightColor;
float4 _LightPosition
//eye properties
float4 _EyePosition;
sampler2D _MainTex;
struct VertexShaderInput {
float4 vertex: POSITION;
float3 normal;
};
struct VertexShaderOutput {
float4 position: SV_POSITION;
half4 color: COLOR;
};
VertexShaderOutput vert(VertexShaderInput vi) {
VertexShaderOutput vso;
//convert Object Space vertex and normal to View Space
float3 viewSpaceVertexPosition = mul(UNITY_MATRIX_MV, vi.vertex).xyz;
float3 viewSpaceNormal = mul(UNITY_MATRIX_IT_MV, float4(vi.normal, 0)).xyz;
//the light and camera/eye are in World Space.. so convert them into Object Space
float4 objectSpaceLightPosition = mul(_World2Object, _LightPosition);
float4 objectSpaceEyePosition = mul(_World2Object, _EyePosition);
//then convert light and camera/eye position to View Space
float3 viewSpaceLightPosition = mul(UNITY_MATRIX_MV, objectSpaceLightPosition).xyz;
float3 viewSpaceEyePosition = mul(UNITY_MATRIX_MV, objectSpaceEyePosition).xyz;
//ambient term
half4 ambient = _Ka;
//diffuse term
float3 viewSpaceLightDirection = normalize(viewSpaceLightPosition - viewSpaceVertexPosition).xyz;
float diffuseLightIntensity = max(dot(viewSpaceNormal, viewSpaceLightDirection), 0);
half4 diffuse = _Kd * _LightColor * diffuseLightIntensity;
//specular term
float3 viewSpaceViewDirection = normalize(viewSpaceEyePosition - viewSpaceVertexPosition).xyz;
float3 viewSpaceHalfVector = normalize(viewSpaceLightDirection + viewSpaceViewDirection).xyz;
float specularLightIntensity = pow(max(dot(viewSpaceNormal, viewSpaceHalfVector), 0), _Shininess);
if (diffuseLightIntensity <= 0)
specularLightIntensity = 0;
half4 specular = _Ks * _LightColor * specularLightIntensity;
//this is what is going to be passed to the fragment shader
vso.color = saturate(ambient + diffuse + specular);
vso.position = mul(UNITY_MATRIX_MVP, vi.vertex);
return vso;
}
half4 frag(VertexShaderOutput vo) : COLOR {
half4 color = vo.color;
return color;
}
ENDCG
}
}
FallBack "Diffuse"
}
The Light in this case, is a small sphere which I am orbiting around my object (the shader is attached to the material of this object). So the _LightPosition is the lightObject.transform.position being sent in the Update() function of a script attached to the object. The _EyePosition is the transform.position of the lone camera in the scene being sent in a similar manner.
The thing I am assuming here (this is where I think I might be going wrong), is that the Light and Eye positions are in world space. That is why I need to do the conversion back to object space and then to view space. I am doing this since there is no view matrix available to take me from world space to view space directly.
So the resulting output that I am getting seems all wrong. The effect on the target object (cube) definitely isn't the correct one. ![alt text][1]
![alt text][2]
The first image is with the light (the small sphere) behind the cube. So the faces seen should not be lit at all. The 2nd image is with the light in front of the cube.
I know this question is rather large by now, but hopefully someone should be able to guide me as to what it is I should be doing to get this correct. I am positive that once this small hurdle is done, I should have a clearer understanding of how this works.
Thanks! [1]: http://www.akshayloke.com/CgShaders/BlinnPhong1.PNG [2]: http://www.akshayloke.com/CgShaders/BlinnPhong2.PNG
Answer by Bunny83 · Sep 30, 2011 at 09:44 PM
You should take a look into the cg-include file, located in your Unity install path ("\Editor\Data\CGIncludes\UnityCG.cginc"). It looks like you pass your own custom parameters to the shader, but Unity already passes tons of global static and object specific information to the shader. Even a lot of helper functions are defined there so you don't have to do all from scratch ;)
It gives you a great overview how Unity handles some stuff. Afaik the source of the built-in shaders are available for download somewhere in the forums. But be careful the syntax have changed a lot between the last Unity versions so some stuff is deprecated.