ucupumar
(ucupumar)
July 6, 2013, 9:23pm
1
I’m trying to create custom specular shader, but always appear to be blocky. Here’s the screenshoot.
Here’s the code.
vs = """
uniform vec3 worldLightDir;
uniform mat4 ModelMatrix;
uniform vec3 cameraPos;
varying vec3 lightDir;
varying vec3 normal;
varying vec3 viewDir;
void main(){
//world space
vec4 pos = ModelMatrix * gl_Vertex;
viewDir = normalize(cameraPos - pos.xyz);
normal = normalize(ModelMatrix * vec4(gl_Normal,0.0)).xyz;
lightDir = worldLightDir;
gl_Position = ftransform();
}
"""
fs = """
varying vec3 normal;
varying vec3 lightDir;
varying vec3 viewDir;
void main(){
vec4 col = vec4(0,0,1,0); //blue
//diffuse
float NdotL = max(0.0,dot(normal,lightDir));
//specular
vec3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0.0,dot(normal,halfVector));
float spec = pow(NdotH,50.0);
col.rgb = col.rgb * NdotL + spec;
//gamma correction
col.rgb = pow(col.rgb,1.0/2.2);
gl_FragColor = col;
}
"""
import bge, mathutils
cont = bge.logic.getCurrentController()
sce = bge.logic.getCurrentScene()
own = cont.owner
mesh = own.meshes[0]
lightDir = sce.lights[0].orientation * mathutils.Vector((0,0,1))
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(vs, fs, 1)
shader.setUniform3f("worldLightDir",lightDir.x,lightDir.y,lightDir.z)
shader.setUniformDef('ModelMatrix', bge.logic.MODELMATRIX)
shader.setUniformDef('cameraPos', bge.logic.CAM_POS)
Here’s the blend . Press ‘p’ in the viewport to see my shader.
I already tried on RenderMonkey using HLSL shader, and the results is the same. :spin:
Did I do something wrong?
Kupoman
(Kupoman)
July 6, 2013, 9:43pm
2
I noticed a couple of things. First, I am pretty sure you want to multiply gl_NormalMatrix with gl_Normal, and not the model matrix. Second, there is no need to have the light direction as a varying if it is the same as the uniform. The fragment shader can accept the uniform instead and save the processing time needed to perform the bilinear interpolation on the constant value (video driver may optimize it).
ucupumar
(ucupumar)
July 6, 2013, 10:22pm
3
Kupoman:
I noticed a couple of things. First, I am pretty sure you want to multiply gl_NormalMatrix with gl_Normal, and not the model matrix. Second, there is no need to have the light direction as a varying if it is the same as the uniform. The fragment shader can accept the uniform instead and save the processing time needed to perform the bilinear interpolation on the constant value (video driver may optimize it).
Thanks for your response.
I’m using gl_NormalMatrix now, but the results still the same. What do you think?
Here’s updated code.
vs = """
uniform vec3 worldLightDir;
uniform mat4 ModelViewMatrix;
uniform mat4 ViewMatrix;
uniform vec3 cameraPos;
varying vec3 lightDir;
varying vec3 normal;
varying vec3 viewDir;
void main(){
//view space
vec4 pos = ModelViewMatrix * gl_Vertex;
vec3 camPos = normalize(ViewMatrix * vec4(cameraPos,1.0)).xyz;
viewDir = normalize(camPos - pos.xyz);
normal = gl_NormalMatrix * gl_Normal;
lightDir = normalize(ViewMatrix * vec4(worldLightDir,0.0)).xyz;
gl_Position = ftransform();
}
"""
fs = """
varying vec3 normal;
varying vec3 lightDir;
varying vec3 viewDir;
void main(){
vec4 col = vec4(0,0,1,0); //blue
//diffuse
float NdotL = max(0.0,dot(normal,lightDir));
//specular
vec3 halfVector = normalize(lightDir + viewDir);
float NdotH = max(0.0,dot(normal,halfVector));
float spec = pow(NdotH,50.0);
col.rgb = col.rgb * NdotL + spec;
//gamma correction
col.rgb = pow(col.rgb,1.0/2.2);
gl_FragColor = col;
}
"""
import bge, mathutils
cont = bge.logic.getCurrentController()
sce = bge.logic.getCurrentScene()
own = cont.owner
mesh = own.meshes[0]
lightDir = sce.lights[0].orientation * mathutils.Vector((0,0,1))
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(vs, fs, 1)
shader.setUniform3f("worldLightDir",lightDir.x,lightDir.y,lightDir.z)
shader.setUniformDef('ModelViewMatrix', bge.logic.MODELVIEWMATRIX)
shader.setUniformDef('ViewMatrix', bge.logic.VIEWMATRIX)
shader.setUniformDef('cameraPos', bge.logic.CAM_POS)
Btw, I’m using Geforce 660, I got compilation error when using Radeon 4850, to fix that you need to change gamma correction code to:
float r = pow(col.r,1.0/2.2);
float g = pow(col.g,1.0/2.2);
float b = pow(col.b,1.0/2.2);
col.rgb = vec3(r,g,b);
ucupumar
(ucupumar)
July 8, 2013, 9:56am
4
Yay! I found the problem, I don’t know why, but you need to normalize normal in fragment shader to make it work. If you normalize normal on vertex shader, you will still get blocky specular. It happens in Rendermonkey too. Anyone know about this issue?
Here’s the screenshoot.
Here’s the code:
vs = """
uniform vec3 worldLightDir;
uniform mat4 ModelViewMatrix;
uniform mat4 ViewMatrix;
uniform vec3 cameraPos;
varying vec3 lightDir;
varying vec3 Normal;
varying vec3 viewDir;
void main(){
//view space
vec4 pos = ModelViewMatrix * gl_Vertex;
vec3 camPos = normalize(ViewMatrix * vec4(cameraPos,1.0)).xyz;
viewDir = normalize(camPos - pos.xyz);
Normal = gl_NormalMatrix * gl_Normal;
lightDir = normalize(ViewMatrix * vec4(worldLightDir,0.0)).xyz;
gl_Position = ftransform();
}
"""
fs = """
varying vec3 Normal;
varying vec3 lightDir;
varying vec3 viewDir;
void main(){
vec4 col = vec4(0,0,1,0); //blue
//diffuse
vec3 normal = normalize(Normal);
float NdotL = max(0.0,dot(normal,lightDir));
//specular
vec3 halfVector = normalize(lightDir + viewDir);
//halfVector = vec3( 0.32506, 0.32506, 0.88808 );
float NdotH = clamp(dot(normal,halfVector),0.0,1.0);
float spec = pow(NdotH,50.0) * 0.5;
col.rgb = col.rgb * NdotL + spec;
//gamma correction
col.r = pow(col.r,1.0/2.2);
col.g = pow(col.g,1.0/2.2);
col.b = pow(col.b,1.0/2.2);
gl_FragColor = col;
}
"""
import bge, mathutils
cont = bge.logic.getCurrentController()
sce = bge.logic.getCurrentScene()
own = cont.owner
mesh = own.meshes[0]
lightDir = sce.lights[0].orientation * mathutils.Vector((0,0,1))
for mat in mesh.materials:
shader = mat.getShader()
if shader != None:
if not shader.isValid():
shader.setSource(vs, fs, 1)
shader.setUniform3f("worldLightDir",lightDir.x,lightDir.y,lightDir.z)
shader.setUniformDef('ModelViewMatrix', bge.logic.MODELVIEWMATRIX)
shader.setUniformDef('ViewMatrix', bge.logic.VIEWMATRIX)
shader.setUniformDef('cameraPos', bge.logic.CAM_POS)
Here’s the blend .
kastoria
(kastoria)
July 8, 2013, 10:53am
5
The reason is because you will get difference is the different mathematics that happen when you normalize in the vertex shader vs the fragment shader.
If you normalize in the vertex shader, then each vertex gets a normal. The normal that gets passed to the fragment shader is the linear average of the normals between vertices. This is not correct math. The linear average of 2 normals on a sphere produces values that are not length 1 and hence, they get dimmer between vertices.
When you normalize in the fragment shader, you get one of these averaged normals and then you can renormalize it “per textel” or some people think of it “per pixel”.
ucupumar
(ucupumar)
July 8, 2013, 8:28pm
6
@Kastoria . Thank you for your explanation. Now I know more.