How to randomize window lighting on skyscrapers without separating each window into a separate object?
Assuming your windows are fairly regularly spaced you can separate the individual 'windows' in the window material by using a Modulo function to convert each of the X,Y,Z Object coordinates into a 'stepped' function. This separates the surface into individual 'cells'. By carefully setting the spacing of the 'cells' (the Modulo functions) to match the positions of the windows you can ensure each 'window' has a consistent solid illumination.

The 'stepped' coordinates are then randomized via a Noise texture and Greater Than used to illuminate only those above a threshold (higher threshold results in fewer 'lit' windows).

Further combine the Object Info 'Random' property into the coordinates (multiplied by a suitable amount so as to make the different objects wildly different) and you have random 'cells' per "building".

This produces the following material :



With the following result (this is showing the 'lights' only - obviously the surrounding building needs to also be included in your final mesh) :


Note : Don't forget to Apply Scale so that the lighting 'cells' are the same dimensions over each object.

For less regularly spaced 'windows' you will need to adjust the Maths nodes to produce the required arrangement of 'window cells'.



There is another way, borrowing a technique used in Per face vertex coloring - essentially to create a UV map where each face is represented by only one pixel in the image. This UV map can then be used as coordinates in the noise texture used to determine whether each 'window' is illuminated.

There are two ways of achieving this :


Using a script


Paste the following script into a Text Editor window :

# Run this script to generate a UV map and Image with 1 pixel per face so each face has a single colour.

import bpy
import bmesh
import math

# Get selected/active object
obj = bpy.context.object
obj.update_from_editmode()
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(obj.data)

# Get count of faces
num_faces = len(bm.faces)

# Create UV map
obj.data.uv_textures.new("facepixels_"+str(num_faces))
uv_layer = bm.loops.layers.uv[0]
bm.faces.layers.tex.verify()

# Process each face and position each loop vertex around the centre point
faceidx = -1
for f in bm.faces:
    faceidx = faceidx + 1
    loopidx = -1
    numverts = len(f.loops)
    for l in f.loops:
        loopidx = loopidx + 1
        luv = l[uv_layer]
        luv.uv[0] = float(faceidx)
        luv.uv[1] = 0.5


Select your 'windows' object (with one face per window) and click 'Run Script'. This will generate a new UV map with 1 pixel per face.


Manually


Unwrap the 'windows' mesh using 'Lightmap pack'.



Then in the UV Editor select all the faces, ensure 'Individual Origins' transformation mode is set and scale to zero (Select all (A) then scale to zero with S0Enter). This should produce the following result :



Note how each face has become a single point in the UV.

You can now use the UV Map (whether created manually or via the above unwrapping method) in the following material :



The Add and Multiply adjust the 'Random' object info value to adjust the noise for each separate mesh (building) - you can adjust those to get different random patterns. Adjust the Greater Than node to affect the threshold for which windows are illuminated for more/fewer lights.