*原创文章,转载请注明出处*
openGL CG 系列教程04 – Lighting + Texture
前几个教程详细的讲解了使用可编程渲染管线实现如何实现光照。如果我们要渲染的模型是带有纹理,要想带有纹理的模型也应用于光照又该怎样实现呢?有的同学可能知道,在openGL的固定管线也可以设置TextureEnvMode模式为GL_MODULATE就可以实现光照和纹理颜色的混合。下面我们就用Cg来实现这个功能。
整个程序将在教程03的基础上加上贴图来实现。为了使用贴图,首先要向vertex shader传入贴图坐标,然后再由vertex shader将贴图坐标传到fragment shader中。在vertex shader的入口函数简单的添加一个传入参数
float2 oTexCoord: TEXCOORD0 |
即可。float2 代表我们使用的2D贴图,语义TEXCOORD0表示将贴图坐标保存在第0个贴图坐标中。传入纹理坐标后,只需要直接将它传个fragment shader,于是我们在vertex shader的输出结构体中也要添加贴图坐标。
struct output { float4 position : POSITION; float2 TexCoord : TEXCOORD0; float3 objectPos : TEXCOORD1; float3 normal : TEXCOORD2; }; |
上面的代码段中添加贴图坐标,其它的数据都是为了计算光照而传入fragment shader的。除了向fragment shader传入贴图坐标,还要把纹理数据传入到fragment shader中。由于纹理数据是个外部数据,我们使用uniform标识符来标识要传入的fragment shader的纹理数据。
uniform sampler2D Texture |
这样向fragment shader传入了纹理坐标和纹理数据后,就可以使用Cg的内置函数tex2D来对每个像素的颜色进行插值,该函数会根据像素的坐标值在纹理数据中查询正确的颜色,然后返回。使用tex2D返回一个纹理的颜色值Ct,然后用Ct和前面通过光照计算得到的颜色值Cl通过混合操作即可。要实现GL_MODULATE的效果,可以简单的通过下面的公式
C = Ct • Cl
A = At • Al
这里C表示混合后像素的颜色的RGB分量,A表示混合后像素颜色的alpha分量。Ct,Cl表示查询得到了纹理颜色的RGB分量和光照计算后像素颜色的RGB分量,At和Al当然就是它们对应的alpha分量了。颜色之间的“•”表示RGB颜色对应的RBG分量分别相乘。下面的图显示了这种混合的效果。
Lighting | + |
Texture | = |
Lighting+Texture |
从中间的图中可以看到带有纹理的茶壶没有任何阴影,看起来没有真实感。但是加上光照后,镜面反射和阴影部分都在贴图中反映出来了。
下面是完整的vertex shader和fragment shader代码。
04vs.cg
struct output { float4 position : POSITION; float3 objectPos: TEXCOORD1; float3 normal : TEXCOORD2; float2 TexCoord : TEXCOORD0; };
output vs_main( float4 position : POSITION, float3 normal : NORMAL, float2 oTexCoord: TEXCOORD0, uniform float4x4 MV, uniform float4x4 MVP ) { output OUT;
OUT.position = mul(MVP, position); OUT.objectPos = mul(MV, position).xyz; OUT.normal = mul(MV, float4(normal,0.0)).xyz; OUT.TexCoord = oTexCoord;
return OUT; } |
04fs.cg
uniform float3 LightPosition; uniform float3 eyePosition; uniform float3 I; uniform float3 Ka; uniform float3 Kd; uniform float3 Ks; uniform float shininess;
struct input { float3 objectPos: TEXCOORD1; float3 normal : TEXCOORD2; float2 texCoord : TEXCOORD0; };
struct output { float4 color : COLOR; };
output fs_main( in input IN, uniform sampler2D Texture) { output OUT;
float3 N = normalize(IN.normal); float3 P = IN.objectPos;
float3 L = normalize(LightPosition - P); float NdotL = max(dot(N,L),0);
float3 ambient = Ka * I; float3 diffuse = Kd * I * NdotL;
float3 V = normalize(eyePosition - P); float3 H = normalize(L+V); float NdotH = pow(max(dot(N,H), 0), shininess);
if(NdotL<=0) NdotH = 0.0; float3 specular = Ks*I*NdotH;
float3 tex = tex2D(Texture, IN.texCoord).xyz; float3 light = ambient + diffuse + specular; OUT.color.xyz= light * tex; OUT.color.w = 1.0;
return OUT; } |
可以看到整个vertex shader和fragment shader跟教程03中的代码几乎一样,唯一的差别就是多了传入贴图坐标,纹理和混合颜色三个部分。可编程渲染管线相当灵活,在fragment shader里完全可以根据需要进行不同类型的颜色插值和混合操作来得到不同的效果。
*原创文章,转载请注明出处*