OpenGL超级宝典笔记——片段着色器(一)
在多数情况下,片段着色器和顶点着色器会成对出现。片段着色器扮演着显示的角色,而顶点着色器则在后面提供支持。为了减少工作,提升性能,尽可能把可以在顶点着色器阶段处理的工作放在在顶点着色器阶段处理。因为顶点着色器处理后经过插值后的片段工作量就大了。 试想一下一个三角形只有三个顶点,而光栅化后的片段就多了。 可以认为顶点着色器是生产者,片段着色器是消费者。
颜色转换
灰度图
有时我们可能需要把彩色图转换成灰度图。在下面的例子中我们使用NTSC的标准把彩图转成灰度图。根据R,G,B的强度转换成相应的灰度。
grayscale.fs
#version 120
void main(void)
{
//更具NTSC标准换算回度
float gray = dot(gl_Color.rgb, vec3(0.299,0.587, 0.114));
//赋值给片段颜色
gl_FragColor = vec4(gray, gray, gray, 1.0);
}
在这里最关键的是输出片段颜色,这个片段颜色经过后面的OpenGL管道处理后最终输出到帧缓冲区中。
棕色图
棕色图有种复古的感觉,是在灰色图的基础上再乘以一个向量(增加某些成分和减少某些成分)。
speia.fs
#version 120
void main(void)
{
//更具NTSC标准换算回度
float gray = dot(gl_Color.rgb, vec3(0.299,0.587, 0.114));
//赋值给片段颜色
gl_FragColor = vec4(gray * vec3(1.2, 1.0, 0.8), 1.0);
}
反转
对颜色进行反转。黑色变成白色。着色器代码也非常简单。
#version 120
void main(void)
{
//用1.0减去每个颜色成分,得到反转色
gl_FragColor = vec3(1.0) - gl_Color.rgb;
//alpha值赋值为1.0
gl_FragColor.a = 1.0;
}
热度标识
在这个例子中用灰度值作为热度值,构建一个1D的从黑到红纹理,以这个热度值作为纹理坐标去查找相应的纹理颜色。纹理颜色的渐变是从黑色到蓝色,蓝色到绿色,绿色到黄色,黄色到红色。
纹理创建函数:
GLvoid CreateHeatSigMap()
{
GLfloat texels[512 * 4];
GLint texSize = (maxTexSize > 512) ? 512 : maxTexSize;
GLint x;
GLfloat p;
for (x = 0; x < texSize; x++)
{
p = (float)((double)x / (double)(texSize-1));
// Gradient from black to blue to green to yellow to red
if (p < 0.25f)
{
// black to blue
p *= 4.0f;
texels[x*4+0] = 0.0f;
texels[x*4+1] = 0.0f;
texels[x*4+2] = p;
}
else if (p < 0.5f)
{
// blue to green
p -= 0.25f;
p *= 4.0f;
texels[x*4+0] = 0.0f;
texels[x*4+1] = p;
texels[x*4+2] = 1.0f - p;
}
else if (p < 0.75f)
{
// green to yellow
p -= 0.5f;
p *= 4.0f;
texels[x*4+0] = p;
texels[x*4+1] = 1.0f;
texels[x*4+2] = 0.0f;
}
else
{
// yellow to red
p -= 0.75f;
p *= 4.0f;
texels[x*4+0] = 1.0f;
texels[x*4+1] = 1.0f - p;
texels[x*4+2] = 0.0f;
}
texels[x*4+3] = 1.0f;
}
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA8, texSize, 0, GL_RGBA, GL_FLOAT, texels);
}
//heatsig.fs
#version 120
//采样哪一个纹理
uniform sampler1D sampler0;
void main(void)
{
float gray = dot(gl_Color.rgb, vec3(0.299, 0.587, 0.114));
//查找热度值
gl_FragColor = texture1D(sampler0, gray);
}
vec4 texture1D(sampler1D s, float coord [, float bias]); 第一个参数是被采样的纹理单元,第二个是纹理坐标。后面是可选的用于mipmap的细节层的偏移
由于我们只使用一个纹理,所以在代码中设置sampler0为0就可以了。
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
CreateHeatSigMap();
uniformLoc = glGetUniformLocation(program[wichShader], "sampler0");
if (uniformLoc != -1)
{
glUniform1i(uniformLoc, 0);
}
依赖性纹理查找
在固定函数管道中,纹理映射是非常严格和死板的,纹理查找是基于插值后的顶点纹理坐标。现在可编程管线提供了更大的灵活性,纹理坐标完全可以由我们手工计算得到。甚至我们可以用前一次纹理查找的值作为下一次的纹理坐标,如此这次的纹理查找的值就依赖上一次的纹理查找。其实在上面的例子中也体现了依赖性,我们的纹理坐标是根据片段的RGB值计算出来的灰度值,即纹理是依赖于这个灰度值的。
这个依赖是可以多层次的,A依赖于B,B又依赖于C。这个依赖链的长度是有限制的,根据不同的实现有不同的限制。
基于片段的雾
在固定函数管线中,雾的计算是基于顶点的,而且雾函数是固定的那么几种。在可编程管线中,我们可以基于片段来计算雾因子。相比于基于顶点的方式,基于片段计算雾会更加精确。下面的例子是用GL_EXP2的雾模式,来计算片段雾。
下面是片段着色器代码:
//fog.fs
#version 120
uniform float density;
uniform vec4 fogColor;
void main(void)
{
//计算雾因子公式 GL_EXP2 f = exp(-(d * c)2)
float fogFactor = ( density * gl_FragCoord.z);
fogFactor *= fogFactor;
const float e = 2.718281;
fogFactor = clamp(pow(e, -fogFactor), 0.0, 1.0);
gl_FragColor = mix(fogColor, gl_Color, fogFactor);
}
其中mix函数是混合两个变量fogColor, gl_Color 第三个参数fogFactor是0.0 到 1.0的范围。计算公式是 fogColor * (1-fogFactor) + gl_Color * fogColor;
其中density 雾密度 和fogColor 雾颜色,作为一致变量由外部传入。如果一些变量是永远不变的,最后是声明为const常量。这样编译器会对其进行优化,提升性能。
设置雾颜色和密度的部分代码
if (whichShader == FOG)
{
glClearColor(fogColor[0], fogColor[1], fogColor[2], fogColor[3]);
}
else
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
...
uniformLoc = glGetUniformLocation(program[whichShader], "density");
if (uniformLoc != -1)
{
glUniform1f(uniformLoc, density);
}
uniformLoc = glGetUniformLocation(program[whichShader], "fogColor");
if (uniformLoc != -1)
{
glUniform4fv(uniformLoc, 1, fogColor);
}
转载于:https://my.oschina.net/sweetdark/blog/222211