[SOLVED] Diffuse map layering two textures with alpha

So I need a way to layer an alpha enabled texture onto a non-transparent texture. A good example would be how voxel based games add braking patterns to blocks:

I know I’ll need to make a custom matdef and sort out the blending in the frag shader, but before I dedicate a week to learning glsl I’d like to review some already made options. (I’m sure of you must have this exact thing lying around somewhere collecting dust)

Some old threads mention that shaderblow has this, but I don’t have the time right now to check. Others have a bunch of 404 links or are about terrain texture splatting.

Any suggestions would be very welcome. :smile:

Well, in a block world game the mesh is batched. The absolute easiest way to achieve this effect is with a second mesh that simple has the faces that you want ‘broken’ and give that a different texture… set blend mode to alpha and depth test to ‘equals’ or whatever (must be at least equals or less than or equal else the z-buffer will fight you).

Nice thing is you can include as many faces as you want in that mesh… whether all sides of one block or a whole mess of blocks.

No custom material is required for that approach.

Alright perhaps that was a bad example for my case, since I’ll be applying this to whole, sometimes non-box meshes. But this is an interesting idea nonetheless.

If I understand you correctly, this approach would involve creating quads with the alpha texture and positioning them exactly on the face?

Wait, isn’t the depth test parameter a boolean?

Yes. The other cases for true texture splatting are much more complicated.

In 3.1 you can set the depth test function. (I guess the online docs are still 3.0)

    /**
     * Set the depth conparison function to the given TestFunction 
     * default is LessOrEqual (GL_LEQUAL)
     * @see TestFunction
     * @see RenderState#setDepthTest(boolean) 
     * @param depthFunc the depth comparison function
     */
    public void setDepthFunc(TestFunction depthFunc) {       

Well I’m, uhm, still on 3.0… Yeah.

Then you will have to do something more complicated, I guess… or wait to do this until you can upgrade.

Isn’t the default for the transparent queue “less or equal” ?
Then it should work out of the box, shouldn’t it?.
I would try that first.

And if your cube is transparent too (e.g. “glass”), then just add the broken pattern after the glass was added. The order in which you attach it to the parent node is the order in which it’s being processed by jME.

Pretty sure, no.

No, objects are sorted. You cannot easily predict the order and it generally has nothing to do with the order that the objects were added. Within a single mesh, yes… but given that this is generally a one-off quick and animated thing (using the minecraft example) you’d want this to be a separate mesh.

Anyway, one or the other will have to be drawn first and one or the other will then fill the zbuffer and (assuming equals is not part of the test function) will hide the second quad.

Okay, then it’s clear - a clever shader or trick or jME 3.1 alpha.
It’s strange that this feature isn’t part of jME 3.0 (I remember version 2 had it).
But okay, things are as they are.

Kinda skimmed the topic. I have a material that has a “Diffuse Overlay” parameter that I butchered together from someone elses work. I use it to apply blood overlays onto people who are injured. Would this be of use to you? If so I can dig it up.

1 Like

That would be really awesome of you. :smiley:

Ok I hope this is the right files…

assets\MatDefs\Lighting.j3md
http://pastie.org/10532318

assets\Shaders\Lighting.frag
http://pastie.org/10532320

assets\Shaders\Lighting.vert
http://pastie.org/10532321

Yes, I am lazy and didn’t even change the name from the files I based it on. Still works fine for me though.
Just in case it saves you any time, here is what I was doing to apply the different overlays in game,

public static Texture bloodm1 = Main.asset.loadTexture(new TextureKey(“Textures/Misc/Blood/bloodm1.PNG”,false)); (without the false it was flipped or something)

mat.setTexture(“DiffuseOverlay”, bloodm1);

Hope it works for you

(credit to @eightonegulf who provided the instructions to make this)

3 Likes

Sorry, to bother you about this now (I didn’t have time to try it out before) but I seem to get a compilation error on the frag shader:

WARNING: Bad compile of:
1	#define DIFFUSEMAP 1
2	#define DIFFUSEOVERLAY 1
3	#define NORMALMAP 1
4	#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING)    
5	    vec2 steepParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){
6	        vec2 vParallaxDirection = normalize(  vViewDir.xy );
7	
8	        // The length of this vector determines the furthest amount of displacement: (Ati's comment)
9	        float fLength         = length( vViewDir );
10	        float fParallaxLength = sqrt( fLength * fLength - vViewDir.z * vViewDir.z ) / vViewDir.z; 
11	
12	        // Compute the actual reverse parallax displacement vector: (Ati's comment)
13	        vec2 vParallaxOffsetTS = vParallaxDirection * fParallaxLength;
14	
15	        // Need to scale the amount of displacement to account for different height ranges
16	        // in height maps. This is controlled by an artist-editable parameter: (Ati's comment)              
17	        parallaxScale *=0.3;
18	        vParallaxOffsetTS *= parallaxScale;
19	
20	       vec3 eyeDir = normalize(vViewDir).xyz;   
21	
22	        float nMinSamples = 6.0;
23	        float nMaxSamples = 1000.0 * parallaxScale;   
24	        float nNumSamples = mix( nMinSamples, nMaxSamples, 1.0 - eyeDir.z );   //In reference shader: int nNumSamples = (int)(lerp( nMinSamples, nMaxSamples, dot( eyeDirWS, N ) ));
25	        float fStepSize = 1.0 / nNumSamples;   
26	        float fCurrHeight = 0.0;
27	        float fPrevHeight = 1.0;
28	        float fNextHeight = 0.0;
29	        float nStepIndex = 0.0;
30	        vec2 vTexOffsetPerStep = fStepSize * vParallaxOffsetTS;
31	        vec2 vTexCurrentOffset = texCoord;
32	        float  fCurrentBound     = 1.0;
33	        float  fParallaxAmount   = 0.0;   
34	
35	        while ( nStepIndex < nNumSamples && fCurrHeight <= fCurrentBound ) {
36	            vTexCurrentOffset -= vTexOffsetPerStep;
37	            fPrevHeight = fCurrHeight;
38	            
39	           
40	           #ifdef NORMALMAP_PARALLAX
41	               //parallax map is stored in the alpha channel of the normal map         
42	               fCurrHeight = texture2D( parallaxMap, vTexCurrentOffset).a; 
43	           #else
44	               //parallax map is a texture
45	               fCurrHeight = texture2D( parallaxMap, vTexCurrentOffset).r;                
46	           #endif
47	           
48	            fCurrentBound -= fStepSize;
49	            nStepIndex+=1.0;
50	        } 
51	        vec2 pt1 = vec2( fCurrentBound, fCurrHeight );
52	        vec2 pt2 = vec2( fCurrentBound + fStepSize, fPrevHeight );
53	
54	        float fDelta2 = pt2.x - pt2.y;
55	        float fDelta1 = pt1.x - pt1.y;
56	
57	        float fDenominator = fDelta2 - fDelta1;
58	
59	        fParallaxAmount = (pt1.x * fDelta2 - pt2.x * fDelta1 ) / fDenominator;
60	
61	        vec2 vParallaxOffset = vParallaxOffsetTS * (1.0 - fParallaxAmount );
62	       return texCoord - vParallaxOffset;  
63	    }
64	
65	    vec2 classicParallaxOffset(sampler2D parallaxMap, vec3 vViewDir,vec2 texCoord,float parallaxScale){ 
66	       float h;
67	       h = texture2D(parallaxMap, texCoord).a;
68	       #ifdef NORMALMAP_PARALLAX
69	               //parallax map is stored in the alpha channel of the normal map         
70	               h = texture2D(parallaxMap, texCoord).a;               
71	       #else
72	               //parallax map is a texture
73	               h = texture2D(parallaxMap, texCoord).r;
74	       #endif
75	       float heightScale = parallaxScale;
76	       float heightBias = heightScale* -0.6;
77	       vec3 normView = normalize(vViewDir);       
78	       h = (h * heightScale + heightBias) * normView.z;
79	       return texCoord + (h * normView.xy);
80	    }
81	#endif
82	#ifdef SPHERE_MAP
83	#define ENVMAP sampler2D
84	#define TEXENV texture2D
85	#else
86	#define ENVMAP samplerCube
87	#define TEXENV textureCube
88	#endif
89	
90	// converts a normalized direction vector
91	// into a texture coordinate for fetching
92	// texel from a sphere map
93	vec2 Optics_SphereCoord(in vec3 dir){
94	    float dzplus1 = dir.z + 1.0;
95	
96	    // compute 1/2p
97	    // NOTE: this simplification only works if dir is normalized.
98	    float inv_two_p = 1.414 * sqrt(dzplus1);
99	    //float inv_two_p = sqrt(dir.x * dir.x + dir.y * dir.y + dzplus1 * dzplus1);
100	    inv_two_p *= 2.0;
101	    inv_two_p = 1.0 / inv_two_p;
102	
103	    // compute texcoord
104	    return (dir.xy * vec2(inv_two_p)) + vec2(0.5);
105	}
106	
107	vec4 Optics_GetEnvColor(in ENVMAP envMap, in vec3 dir){
108	    #ifdef SPHERE_MAP
109	    return texture2D(envMap, Optics_SphereCoord(dir));
110	    #else
111	    return textureCube(envMap, dir);
112	    #endif
113	}
114	#define ATTENUATION
115	//#define HQ_ATTENUATION
116	
117	varying vec2 texCoord;
118	#ifdef SEPARATE_TEXCOORD
119	  varying vec2 texCoord2;
120	#endif
121	
122	varying vec3 AmbientSum;
123	varying vec4 DiffuseSum;
124	varying vec3 SpecularSum;
125	
126	#ifndef VERTEX_LIGHTING
127	  uniform vec4 g_LightDirection;
128	  //varying vec3 vPosition;
129	  varying vec3 vViewDir;
130	  varying vec4 vLightDir;
131	  varying vec3 lightVec;
132	#else
133	  varying vec2 vertexLightValues;
134	#endif
135	
136	#ifdef DIFFUSEMAP
137	  uniform sampler2D m_DiffuseMap;
138	#endif
139	
140	#ifdef DIFFUSEOVERLAY
141	uniform sampler2D m_DiffuseOverlay;
142	#endif
143	
144	#ifdef SPECULARMAP
145	  uniform sampler2D m_SpecularMap;
146	#endif
147	
148	#ifdef PARALLAXMAP
149	  uniform sampler2D m_ParallaxMap;  
150	#endif
151	#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) 
152	    uniform float m_ParallaxHeight;
153	#endif
154	
155	#ifdef LIGHTMAP
156	  uniform sampler2D m_LightMap;
157	#endif
158	  
159	#ifdef NORMALMAP
160	  uniform sampler2D m_NormalMap;   
161	#else
162	  varying vec3 vNormal;
163	#endif
164	
165	#ifdef ALPHAMAP
166	  uniform sampler2D m_AlphaMap;
167	#endif
168	
169	#ifdef COLORRAMP
170	  uniform sampler2D m_ColorRamp;
171	#endif
172	
173	uniform float m_AlphaDiscardThreshold;
174	
175	#ifndef VERTEX_LIGHTING
176	uniform float m_Shininess;
177	
178	#ifdef HQ_ATTENUATION
179	uniform vec4 g_LightPosition;
180	#endif
181	
182	#ifdef USE_REFLECTION 
183	    uniform float m_ReflectionPower;
184	    uniform float m_ReflectionIntensity;
185	    varying vec4 refVec;
186	
187	    uniform ENVMAP m_EnvMap;
188	#endif
189	
190	float tangDot(in vec3 v1, in vec3 v2){
191	    float d = dot(v1,v2);
192	    #ifdef V_TANGENT
193	        d = 1.0 - d*d;
194	        return step(0.0, d) * sqrt(d);
195	    #else
196	        return d;
197	    #endif
198	}
199	
200	float lightComputeDiffuse(in vec3 norm, in vec3 lightdir, in vec3 viewdir){
201	    #ifdef MINNAERT
202	        float NdotL = max(0.0, dot(norm, lightdir));
203	        float NdotV = max(0.0, dot(norm, viewdir));
204	        return NdotL * pow(max(NdotL * NdotV, 0.1), -1.0) * 0.5;
205	    #else
206	        return max(0.0, dot(norm, lightdir));
207	    #endif
208	}
209	
210	float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){
211	    // NOTE: check for shiny <= 1 removed since shininess is now 
212	    // 1.0 by default (uses matdefs default vals)
213	    #ifdef LOW_QUALITY
214	       // Blinn-Phong
215	       // Note: preferably, H should be computed in the vertex shader
216	       vec3 H = (viewdir + lightdir) * vec3(0.5);
217	       return pow(max(tangDot(H, norm), 0.0), shiny);
218	    #elif defined(WARDISO)
219	        // Isotropic Ward
220	        vec3 halfVec = normalize(viewdir + lightdir);
221	        float NdotH  = max(0.001, tangDot(norm, halfVec));
222	        float NdotV  = max(0.001, tangDot(norm, viewdir));
223	        float NdotL  = max(0.001, tangDot(norm, lightdir));
224	        float a      = tan(acos(NdotH));
225	        float p      = max(shiny/128.0, 0.001);
226	        return NdotL * (1.0 / (4.0*3.14159265*p*p)) * (exp(-(a*a)/(p*p)) / (sqrt(NdotV * NdotL)));
227	    #else
228	       // Standard Phong
229	       vec3 R = reflect(-lightdir, norm);
230	       return pow(max(tangDot(R, viewdir), 0.0), shiny);
231	    #endif
232	}
233	
234	vec2 computeLighting(in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){
235	   float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir);
236	   float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess);
237	
238	   #ifdef HQ_ATTENUATION
239	    float att = clamp(1.0 - g_LightPosition.w * length(lightVec), 0.0, 1.0);
240	   #else
241	    float att = vLightDir.w;
242	   #endif
243	
244	   if (m_Shininess <= 1.0) {
245	       specularFactor = 0.0; // should be one instruction on most cards ..
246	   }
247	
248	   specularFactor *= diffuseFactor;
249	
250	   return vec2(diffuseFactor, specularFactor) * vec2(att);
251	}
252	#endif
253	
254	void main(){
255	    vec2 newTexCoord;
256	     
257	    #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) 
258	     
259	       #ifdef STEEP_PARALLAX
260	           #ifdef NORMALMAP_PARALLAX
261	               //parallax map is stored in the alpha channel of the normal map         
262	               newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
263	           #else
264	               //parallax map is a texture
265	               newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);         
266	           #endif
267	       #else
268	           #ifdef NORMALMAP_PARALLAX
269	               //parallax map is stored in the alpha channel of the normal map         
270	               newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
271	           #else
272	               //parallax map is a texture
273	               newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
274	           #endif
275	       #endif
276	    #else
277	       newTexCoord = texCoord;    
278	    #endif
279	    
280	   #ifdef DIFFUSEMAP
281	      vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord);
282	      #ifdef DIFFUSEOVERLAY
283	      vec4 alphaBlend = texture2D( m_DiffuseOverlay, texCoord2.xy );
284	      vec4 diffuseOverlay = texture2D(m_DiffuseOverlay, texCoord2);
285	      diffuseColor = mix( diffuseColor, diffuseOverlay, alphaBlend.a );
286	      #endif
287	    #else
288	      vec4 diffuseColor = vec4(1.0);
289	    #endif
290	
291	    float alpha = DiffuseSum.a * diffuseColor.a;
292	    #ifdef ALPHAMAP
293	       alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r;
294	    #endif
295	    if(alpha < m_AlphaDiscardThreshold){
296	        discard;
297	    }
298	
299	    #ifndef VERTEX_LIGHTING
300	        float spotFallOff = 1.0;
301	
302	        #if __VERSION__ >= 110
303	          // allow use of control flow
304	          if(g_LightDirection.w != 0.0){
305	        #endif
306	
307	          vec3 L       = normalize(lightVec.xyz);
308	          vec3 spotdir = normalize(g_LightDirection.xyz);
309	          float curAngleCos = dot(-L, spotdir);             
310	          float innerAngleCos = floor(g_LightDirection.w) * 0.001;
311	          float outerAngleCos = fract(g_LightDirection.w);
312	          float innerMinusOuter = innerAngleCos - outerAngleCos;
313	          spotFallOff = (curAngleCos - outerAngleCos) / innerMinusOuter;
314	
315	          #if __VERSION__ >= 110
316	              if(spotFallOff <= 0.0){
317	                  gl_FragColor.rgb = AmbientSum * diffuseColor.rgb;
318	                  gl_FragColor.a   = alpha;
319	                  return;
320	              }else{
321	                  spotFallOff = clamp(spotFallOff, 0.0, 1.0);
322	              }
323	             }
324	          #else
325	             spotFallOff = clamp(spotFallOff, step(g_LightDirection.w, 0.001), 1.0);
326	          #endif
327	     #endif
328	 
329	    // ***********************
330	    // Read from textures
331	    // ***********************
332	    #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
333	      vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);
334	      vec3 normal = normalize((normalHeight.xyz * vec3(2.0) - vec3(1.0)));
335	      #ifdef LATC
336	        normal.z = sqrt(1.0 - (normal.x * normal.x) - (normal.y * normal.y));
337	      #endif
338	      //normal.y = -normal.y;
339	    #elif !defined(VERTEX_LIGHTING)
340	      vec3 normal = vNormal;
341	      #if !defined(LOW_QUALITY) && !defined(V_TANGENT)
342	         normal = normalize(normal);
343	      #endif
344	    #endif
345	
346	    #ifdef SPECULARMAP
347	      vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
348	    #else
349	      vec4 specularColor = vec4(1.0);
350	    #endif
351	
352	    #ifdef LIGHTMAP
353	       vec3 lightMapColor;
354	       #ifdef SEPARATE_TEXCOORD
355	          lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
356	       #else
357	          lightMapColor = texture2D(m_LightMap, texCoord).rgb;
358	       #endif
359	       specularColor.rgb *= lightMapColor;
360	       diffuseColor.rgb  *= lightMapColor;
361	    #endif
362	
363	    #ifdef VERTEX_LIGHTING
364	       vec2 light = vertexLightValues.xy;
365	       #ifdef COLORRAMP
366	           light.x = texture2D(m_ColorRamp, vec2(light.x, 0.0)).r;
367	           light.y = texture2D(m_ColorRamp, vec2(light.y, 0.0)).r;
368	       #endif
369	
370	       gl_FragColor.rgb =  AmbientSum     * diffuseColor.rgb + 
371	                           DiffuseSum.rgb * diffuseColor.rgb  * vec3(light.x) +
372	                           SpecularSum    * specularColor.rgb * vec3(light.y);
373	    #else
374	       vec4 lightDir = vLightDir;
375	       lightDir.xyz = normalize(lightDir.xyz);
376	       vec3 viewDir = normalize(vViewDir);
377	
378	       vec2   light = computeLighting(normal, viewDir, lightDir.xyz) * spotFallOff;
379	       #ifdef COLORRAMP
380	           diffuseColor.rgb  *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb;
381	           specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb;
382	       #endif
383	
384	       // Workaround, since it is not possible to modify varying variables
385	       vec4 SpecularSum2 = vec4(SpecularSum, 1.0);
386	       #ifdef USE_REFLECTION
387	            vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz);
388	
389	            // Interpolate light specularity toward reflection color
390	            // Multiply result by specular map
391	            specularColor = mix(SpecularSum2 * light.y, refColor, refVec.w) * specularColor;
392	
393	            SpecularSum2 = vec4(1.0);
394	            light.y = 1.0;
395	       #endif
396	
397	       gl_FragColor.rgb =  AmbientSum       * diffuseColor.rgb  +
398	                           DiffuseSum.rgb   * diffuseColor.rgb  * vec3(light.x) +
399	                           SpecularSum2.rgb * specularColor.rgb * vec3(light.y);
400	    #endif
401	    gl_FragColor.a = alpha;
402	}

nov. 17, 2015 8:21:14 PM com.jme3.app.Application handleError
SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
com.jme3.renderer.RendererException: compile error in:ShaderSource[name=assets/shaders/Lighting.frag, defines, type=Fragment, language=GLSL100] error:0(283) : error C1008: undefined variable "texCoord2"
0(284) : error C1008: undefined variable "texCoord2"

This happens whenever I try to set the DiffuseOverlay texture. Is it something obvious?

I havn’t used it in a while I cant remember how I set it up, clearly a problem with the UV co-ords, think it used a second set. I’ll test it now to see how I got it working

Yeah ok I got it.

So all my characters were UV mapped symmetrically, so I textured only half the character. For the my blood overlay I did not want symmetry so the diffuseOverlay material uses the 2nd set of UV co-ords. You need to check the “SeperateTexCoord” box to use it (if I set the diffuseOverlay I got some spam errors even just in the material window without ticking this). Checking this will stop the error.

If your model doesn’t have a second set of texture coords then it’s gonna look odd. If you can use it with a 2nd set its fine, if not tell me and I will attempt to change the shader to use the only the first set.

Well with SeperateTexCoord enabled it doesn’t crash, but there doesn’t seem to be any overlay showing either. I’m testing this on a basic box geometry btw, and none of my models are mirrored so I can throw that right out of the window.

Ok, here is a replacement Lighting.frag

http://pastie.org/10565324

Working for me without the seperateTexCoord, model only has 1 set

Imgur
(professional quality graphics, if it was 1995)

I still get an error spammed in the IDE when I set the diffuse overlay in the SDK when I open the material, no idea why, but after it’s been set no more problems arise. Let me know if it does/doesn’t work for you

2 Likes

Thanks a lot, it works perfectly now :smiley:

I do have to replace those crappy test damage overlay textures though.

You know, that looks pretty good for today too :smile:

2 Likes